feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
|
|
|
|
/* To generate run: "serverpod generate" */
|
|
|
|
|
|
|
|
|
|
// ignore_for_file: implementation_imports
|
|
|
|
|
// ignore_for_file: library_private_types_in_public_api
|
|
|
|
|
// ignore_for_file: non_constant_identifier_names
|
|
|
|
|
// ignore_for_file: public_member_api_docs
|
|
|
|
|
// ignore_for_file: type_literal_in_constant_pattern
|
|
|
|
|
// ignore_for_file: use_super_parameters
|
|
|
|
|
// ignore_for_file: invalid_use_of_internal_member
|
|
|
|
|
// ignore_for_file: dead_code, unnecessary_type_check
|
|
|
|
|
|
|
|
|
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
|
|
|
|
import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
|
|
|
|
|
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
2026-10-28 23:37:47 +01:00
|
|
|
import 'package:halkyon_train_server/src/generated/routing/route_point.dart'
|
|
|
|
|
as _is4pcfub;
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
import 'package:serverpod/protocol.dart' as _isp;
|
|
|
|
|
import 'package:serverpod/serverpod.dart' as _is;
|
|
|
|
|
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
|
|
|
|
as _iacs;
|
|
|
|
|
import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
|
|
|
|
as _iais;
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
|
|
|
|
as _izehhkf5;
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
import 'day/day.dart' as _ikmv9f3y;
|
|
|
|
|
import 'day/day_series.dart' as _ir1ofny6;
|
|
|
|
|
import 'day/night.dart' as _i17cgxdf;
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
import 'firmware/loop_firmware.dart' as _i4qollgq;
|
|
|
|
|
import 'firmware/loop_firmware_refused.dart' as _ipcwdy0l;
|
2026-10-28 13:42:28 +01:00
|
|
|
import 'map/map_source.dart' as _ikw0qg8x;
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
import 'plan/plan.dart' as _i45prw73;
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
import 'privacy/content_key.dart' as _imed3x5d;
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
import 'profile/profile.dart' as _ia5j8fch;
|
2026-10-28 23:37:47 +01:00
|
|
|
import 'routing/planned_route.dart' as _ikjgxraq;
|
|
|
|
|
import 'routing/route_hint.dart' as _if6974nb;
|
|
|
|
|
import 'routing/route_point.dart' as _isqtfus6;
|
|
|
|
|
import 'routing/route_profile.dart' as _ifl4j5ep;
|
|
|
|
|
import 'routing/route_refused.dart' as _ix4aepwk;
|
|
|
|
|
import 'routing/route_stretch.dart' as _ic7f85bc;
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
import 'workout/workout.dart' as _i5b9b17z;
|
|
|
|
|
import 'workout/workout_series.dart' as _i92mefto;
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
export 'day/day.dart';
|
|
|
|
|
export 'day/day_series.dart';
|
|
|
|
|
export 'day/night.dart';
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
export 'firmware/loop_firmware.dart';
|
|
|
|
|
export 'firmware/loop_firmware_refused.dart';
|
2026-10-28 13:42:28 +01:00
|
|
|
export 'map/map_source.dart';
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
export 'plan/plan.dart';
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
export 'privacy/content_key.dart';
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
export 'profile/profile.dart';
|
2026-10-28 23:37:47 +01:00
|
|
|
export 'routing/planned_route.dart';
|
|
|
|
|
export 'routing/route_hint.dart';
|
|
|
|
|
export 'routing/route_point.dart';
|
|
|
|
|
export 'routing/route_profile.dart';
|
|
|
|
|
export 'routing/route_refused.dart';
|
|
|
|
|
export 'routing/route_stretch.dart';
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
export 'workout/workout.dart';
|
|
|
|
|
export 'workout/workout_series.dart';
|
|
|
|
|
export 'sync_tables.dart';
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
|
|
|
|
|
class Protocol extends _is.DatabaseSerializationManager {
|
|
|
|
|
Protocol._();
|
|
|
|
|
|
|
|
|
|
factory Protocol() => _instance;
|
|
|
|
|
|
|
|
|
|
static final Protocol _instance = Protocol._().._registerHostProtocols();
|
|
|
|
|
|
|
|
|
|
static List<_isp.TableDefinition> get targetTableDefinitions => [
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'content_key',
|
|
|
|
|
dartName: 'ContentKey',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'dataKeyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'String',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'sealedKey',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'content_key_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'content_key_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'day',
|
|
|
|
|
dartName: 'Day',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'day_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'day_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
|
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'day_series',
|
|
|
|
|
dartName: 'DaySeries',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'dayId',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'day_series_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'day_series_fk_1',
|
|
|
|
|
columns: ['dayId'],
|
|
|
|
|
referenceTable: 'day',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'day_series_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'day_series_day_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'dayId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
|
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'night',
|
|
|
|
|
dartName: 'Night',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'night_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'night_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'plan',
|
|
|
|
|
dartName: 'Plan',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'plan_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'plan_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'profile',
|
|
|
|
|
dartName: 'Profile',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'profile_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'profile_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'workout',
|
|
|
|
|
dartName: 'Workout',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'workout_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'workout_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
|
|
|
|
_isp.TableDefinition(
|
|
|
|
|
name: 'workout_series',
|
|
|
|
|
dartName: 'WorkoutSeries',
|
|
|
|
|
schema: 'public',
|
|
|
|
|
module: 'halkyon_train',
|
|
|
|
|
columns: [
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'id',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue?',
|
|
|
|
|
columnDefault: 'random_v7',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'spaceId',
|
|
|
|
|
columnType: _isp.ColumnType.bigint,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'int?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'workoutId',
|
|
|
|
|
columnType: _isp.ColumnType.uuid,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'UuidValue',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'keyId',
|
|
|
|
|
columnType: _isp.ColumnType.text,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'String?',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'payload',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: false,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData',
|
|
|
|
|
),
|
|
|
|
|
_isp.ColumnDefinition(
|
|
|
|
|
name: 'nonce',
|
|
|
|
|
columnType: _isp.ColumnType.bytea,
|
|
|
|
|
isNullable: true,
|
|
|
|
|
dartType: 'dart:typed_data:ByteData?',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
foreignKeys: [
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'workout_series_fk_0',
|
|
|
|
|
columns: ['spaceId'],
|
|
|
|
|
referenceTable: 'offline_sync_spaces',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
),
|
|
|
|
|
_isp.ForeignKeyDefinition(
|
|
|
|
|
constraintName: 'workout_series_fk_1',
|
|
|
|
|
columns: ['workoutId'],
|
|
|
|
|
referenceTable: 'workout',
|
|
|
|
|
referenceTableSchema: 'public',
|
|
|
|
|
referenceColumns: ['id'],
|
|
|
|
|
onUpdate: _isp.ForeignKeyAction.noAction,
|
|
|
|
|
onDelete: _isp.ForeignKeyAction.cascade,
|
|
|
|
|
matchType: null,
|
|
|
|
|
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
indexes: [
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'workout_series_space_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'spaceId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
_isp.IndexDefinition(
|
|
|
|
|
indexName: 'workout_series_workout_idx',
|
|
|
|
|
tableSpace: null,
|
|
|
|
|
elements: [
|
|
|
|
|
_isp.IndexElementDefinition(
|
|
|
|
|
type: _isp.IndexElementDefinitionType.column,
|
|
|
|
|
definition: 'workoutId',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
type: 'btree',
|
|
|
|
|
isUnique: false,
|
|
|
|
|
isPrimary: false,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
managed: true,
|
|
|
|
|
),
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
..._ik8molhd.Protocol.targetTableDefinitions,
|
|
|
|
|
..._iais.Protocol.targetTableDefinitions,
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
..._izehhkf5.Protocol.targetTableDefinitions,
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
..._ipoqfiqr.Protocol.targetTableDefinitions,
|
|
|
|
|
..._iacs.Protocol.targetTableDefinitions,
|
|
|
|
|
..._isp.Protocol.targetTableDefinitions,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
static String? getClassNameFromObjectJson(dynamic data) {
|
|
|
|
|
if (data is! Map) return null;
|
|
|
|
|
final className = data['__className__'] as String?;
|
|
|
|
|
return className;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
T deserialize<T>(
|
|
|
|
|
dynamic data, [
|
|
|
|
|
Type? t,
|
|
|
|
|
]) {
|
|
|
|
|
t ??= T;
|
|
|
|
|
|
|
|
|
|
final dataClassName = getClassNameFromObjectJson(data);
|
|
|
|
|
if (dataClassName != null && dataClassName != getClassNameForType(t)) {
|
|
|
|
|
try {
|
|
|
|
|
return deserializeByClassName({
|
|
|
|
|
'className': dataClassName,
|
|
|
|
|
'data': data,
|
|
|
|
|
});
|
|
|
|
|
} on _is.DeserializationClassNameNotFoundException catch (_) {
|
|
|
|
|
// If the className is not recognized (e.g., older client receiving
|
|
|
|
|
// data with a new subtype), fall back to deserializing without the
|
|
|
|
|
// className, using the expected type T.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
if (t == _ikmv9f3y.Day) {
|
|
|
|
|
return _ikmv9f3y.Day.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _ir1ofny6.DaySeries) {
|
|
|
|
|
return _ir1ofny6.DaySeries.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _i17cgxdf.Night) {
|
|
|
|
|
return _i17cgxdf.Night.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
if (t == _i4qollgq.LoopFirmware) {
|
|
|
|
|
return _i4qollgq.LoopFirmware.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _ipcwdy0l.LoopFirmwareRefused) {
|
|
|
|
|
return _ipcwdy0l.LoopFirmwareRefused.fromJson(data) as T;
|
|
|
|
|
}
|
2026-10-28 13:42:28 +01:00
|
|
|
if (t == _ikw0qg8x.MapSource) {
|
|
|
|
|
return _ikw0qg8x.MapSource.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
if (t == _i45prw73.Plan) {
|
|
|
|
|
return _i45prw73.Plan.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (t == _imed3x5d.ContentKey) {
|
|
|
|
|
return _imed3x5d.ContentKey.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
if (t == _ia5j8fch.Profile) {
|
|
|
|
|
return _ia5j8fch.Profile.fromJson(data) as T;
|
|
|
|
|
}
|
2026-10-28 23:37:47 +01:00
|
|
|
if (t == _ikjgxraq.PlannedRoute) {
|
|
|
|
|
return _ikjgxraq.PlannedRoute.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _if6974nb.RouteHint) {
|
|
|
|
|
return _if6974nb.RouteHint.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _isqtfus6.RoutePoint) {
|
|
|
|
|
return _isqtfus6.RoutePoint.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _ifl4j5ep.RouteProfile) {
|
|
|
|
|
return _ifl4j5ep.RouteProfile.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _ix4aepwk.RouteRefused) {
|
|
|
|
|
return _ix4aepwk.RouteRefused.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _ic7f85bc.RouteStretch) {
|
|
|
|
|
return _ic7f85bc.RouteStretch.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (t == _i5b9b17z.Workout) {
|
|
|
|
|
return _i5b9b17z.Workout.fromJson(data) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _i92mefto.WorkoutSeries) {
|
|
|
|
|
return _i92mefto.WorkoutSeries.fromJson(data) as T;
|
|
|
|
|
}
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
if (t == _is.getType<_ikmv9f3y.Day?>()) {
|
|
|
|
|
return (data != null ? _ikmv9f3y.Day.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_ir1ofny6.DaySeries?>()) {
|
|
|
|
|
return (data != null ? _ir1ofny6.DaySeries.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_i17cgxdf.Night?>()) {
|
|
|
|
|
return (data != null ? _i17cgxdf.Night.fromJson(data) : null) as T;
|
|
|
|
|
}
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
if (t == _is.getType<_i4qollgq.LoopFirmware?>()) {
|
|
|
|
|
return (data != null ? _i4qollgq.LoopFirmware.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_ipcwdy0l.LoopFirmwareRefused?>()) {
|
|
|
|
|
return (data != null
|
|
|
|
|
? _ipcwdy0l.LoopFirmwareRefused.fromJson(data)
|
|
|
|
|
: null)
|
|
|
|
|
as T;
|
|
|
|
|
}
|
2026-10-28 13:42:28 +01:00
|
|
|
if (t == _is.getType<_ikw0qg8x.MapSource?>()) {
|
|
|
|
|
return (data != null ? _ikw0qg8x.MapSource.fromJson(data) : null) as T;
|
|
|
|
|
}
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
if (t == _is.getType<_i45prw73.Plan?>()) {
|
|
|
|
|
return (data != null ? _i45prw73.Plan.fromJson(data) : null) as T;
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (t == _is.getType<_imed3x5d.ContentKey?>()) {
|
|
|
|
|
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
|
|
|
|
|
}
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
if (t == _is.getType<_ia5j8fch.Profile?>()) {
|
|
|
|
|
return (data != null ? _ia5j8fch.Profile.fromJson(data) : null) as T;
|
|
|
|
|
}
|
2026-10-28 23:37:47 +01:00
|
|
|
if (t == _is.getType<_ikjgxraq.PlannedRoute?>()) {
|
|
|
|
|
return (data != null ? _ikjgxraq.PlannedRoute.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_if6974nb.RouteHint?>()) {
|
|
|
|
|
return (data != null ? _if6974nb.RouteHint.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_isqtfus6.RoutePoint?>()) {
|
|
|
|
|
return (data != null ? _isqtfus6.RoutePoint.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_ifl4j5ep.RouteProfile?>()) {
|
|
|
|
|
return (data != null ? _ifl4j5ep.RouteProfile.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_ix4aepwk.RouteRefused?>()) {
|
|
|
|
|
return (data != null ? _ix4aepwk.RouteRefused.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_ic7f85bc.RouteStretch?>()) {
|
|
|
|
|
return (data != null ? _ic7f85bc.RouteStretch.fromJson(data) : null) as T;
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (t == _is.getType<_i5b9b17z.Workout?>()) {
|
|
|
|
|
return (data != null ? _i5b9b17z.Workout.fromJson(data) : null) as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == _is.getType<_i92mefto.WorkoutSeries?>()) {
|
|
|
|
|
return (data != null ? _i92mefto.WorkoutSeries.fromJson(data) : null)
|
|
|
|
|
as T;
|
|
|
|
|
}
|
2026-10-28 23:37:47 +01:00
|
|
|
if (t == List<_isqtfus6.RoutePoint>) {
|
|
|
|
|
return (data as List)
|
|
|
|
|
.map((e) => deserialize<_isqtfus6.RoutePoint>(e))
|
|
|
|
|
.toList()
|
|
|
|
|
as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == List<double>) {
|
|
|
|
|
return (data as List).map((e) => deserialize<double>(e)).toList() as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == List<_if6974nb.RouteHint>) {
|
|
|
|
|
return (data as List)
|
|
|
|
|
.map((e) => deserialize<_if6974nb.RouteHint>(e))
|
|
|
|
|
.toList()
|
|
|
|
|
as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == List<_ic7f85bc.RouteStretch>) {
|
|
|
|
|
return (data as List)
|
|
|
|
|
.map((e) => deserialize<_ic7f85bc.RouteStretch>(e))
|
|
|
|
|
.toList()
|
|
|
|
|
as T;
|
|
|
|
|
}
|
|
|
|
|
if (t == List<_is4pcfub.RoutePoint>) {
|
|
|
|
|
return (data as List)
|
|
|
|
|
.map((e) => deserialize<_is4pcfub.RoutePoint>(e))
|
|
|
|
|
.toList()
|
|
|
|
|
as T;
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
try {
|
|
|
|
|
return _ik8molhd.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
|
|
|
|
try {
|
|
|
|
|
return _iais.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
try {
|
|
|
|
|
return _izehhkf5.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
try {
|
|
|
|
|
return _ipoqfiqr.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
|
|
|
|
try {
|
|
|
|
|
return _iacs.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
|
|
|
|
try {
|
|
|
|
|
return _isp.Protocol().deserialize<T>(data, t);
|
|
|
|
|
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
|
|
|
|
return super.deserialize<T>(data, t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static String? getClassNameForType(Type type) {
|
|
|
|
|
return switch (type) {
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
_ikmv9f3y.Day => 'Day',
|
|
|
|
|
_ir1ofny6.DaySeries => 'DaySeries',
|
|
|
|
|
_i17cgxdf.Night => 'Night',
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
_i4qollgq.LoopFirmware => 'LoopFirmware',
|
|
|
|
|
_ipcwdy0l.LoopFirmwareRefused => 'LoopFirmwareRefused',
|
2026-10-28 13:42:28 +01:00
|
|
|
_ikw0qg8x.MapSource => 'MapSource',
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
_i45prw73.Plan => 'Plan',
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
_imed3x5d.ContentKey => 'ContentKey',
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
_ia5j8fch.Profile => 'Profile',
|
2026-10-28 23:37:47 +01:00
|
|
|
_ikjgxraq.PlannedRoute => 'PlannedRoute',
|
|
|
|
|
_if6974nb.RouteHint => 'RouteHint',
|
|
|
|
|
_isqtfus6.RoutePoint => 'RoutePoint',
|
|
|
|
|
_ifl4j5ep.RouteProfile => 'RouteProfile',
|
|
|
|
|
_ix4aepwk.RouteRefused => 'RouteRefused',
|
|
|
|
|
_ic7f85bc.RouteStretch => 'RouteStretch',
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
_i5b9b17z.Workout => 'Workout',
|
|
|
|
|
_i92mefto.WorkoutSeries => 'WorkoutSeries',
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
_ => null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String? getClassNameForObject(Object? data) {
|
|
|
|
|
String? className = super.getClassNameForObject(data);
|
|
|
|
|
if (className != null) return className;
|
|
|
|
|
|
|
|
|
|
if (data is Map<String, dynamic> && data['__className__'] is String) {
|
|
|
|
|
return (data['__className__'] as String).replaceFirst(
|
|
|
|
|
'halkyon_train.',
|
|
|
|
|
'',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
switch (data) {
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
case _ikmv9f3y.Day():
|
|
|
|
|
return 'Day';
|
|
|
|
|
case _ir1ofny6.DaySeries():
|
|
|
|
|
return 'DaySeries';
|
|
|
|
|
case _i17cgxdf.Night():
|
|
|
|
|
return 'Night';
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
case _i4qollgq.LoopFirmware():
|
|
|
|
|
return 'LoopFirmware';
|
|
|
|
|
case _ipcwdy0l.LoopFirmwareRefused():
|
|
|
|
|
return 'LoopFirmwareRefused';
|
2026-10-28 13:42:28 +01:00
|
|
|
case _ikw0qg8x.MapSource():
|
|
|
|
|
return 'MapSource';
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
case _i45prw73.Plan():
|
|
|
|
|
return 'Plan';
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
case _imed3x5d.ContentKey():
|
|
|
|
|
return 'ContentKey';
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
case _ia5j8fch.Profile():
|
|
|
|
|
return 'Profile';
|
2026-10-28 23:37:47 +01:00
|
|
|
case _ikjgxraq.PlannedRoute():
|
|
|
|
|
return 'PlannedRoute';
|
|
|
|
|
case _if6974nb.RouteHint():
|
|
|
|
|
return 'RouteHint';
|
|
|
|
|
case _isqtfus6.RoutePoint():
|
|
|
|
|
return 'RoutePoint';
|
|
|
|
|
case _ifl4j5ep.RouteProfile():
|
|
|
|
|
return 'RouteProfile';
|
|
|
|
|
case _ix4aepwk.RouteRefused():
|
|
|
|
|
return 'RouteRefused';
|
|
|
|
|
case _ic7f85bc.RouteStretch():
|
|
|
|
|
return 'RouteStretch';
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
case _i5b9b17z.Workout():
|
|
|
|
|
return 'Workout';
|
|
|
|
|
case _i92mefto.WorkoutSeries():
|
|
|
|
|
return 'WorkoutSeries';
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
className = _ik8molhd.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.') ? className : 'halkyon_base.$className';
|
|
|
|
|
}
|
|
|
|
|
className = _iais.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.')
|
|
|
|
|
? className
|
|
|
|
|
: 'serverpod_auth_idp.$className';
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
className = _izehhkf5.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.')
|
|
|
|
|
? className
|
|
|
|
|
: 'serverpod_offline_sync.$className';
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
className = _ipoqfiqr.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.') ? className : 'halkyon_oidc.$className';
|
|
|
|
|
}
|
|
|
|
|
className = _iacs.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.')
|
|
|
|
|
? className
|
|
|
|
|
: 'serverpod_auth_core.$className';
|
|
|
|
|
}
|
|
|
|
|
className = _isp.Protocol().getClassNameForObject(data);
|
|
|
|
|
if (className != null) {
|
|
|
|
|
return className.contains('.') ? className : 'serverpod.$className';
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
dynamic deserializeByClassName(Map<String, dynamic> data) {
|
|
|
|
|
var dataClassName = data['className'];
|
|
|
|
|
if (dataClassName is! String) {
|
|
|
|
|
return super.deserializeByClassName(data);
|
|
|
|
|
}
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
if (dataClassName == 'Day') {
|
|
|
|
|
return deserialize<_ikmv9f3y.Day>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'DaySeries') {
|
|
|
|
|
return deserialize<_ir1ofny6.DaySeries>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'Night') {
|
|
|
|
|
return deserialize<_i17cgxdf.Night>(data['data']);
|
|
|
|
|
}
|
feat(train): the Loop's firmware updates, directly or through the instance (ADR 0030, D10, D40)
Train's server relays Polar's firmware service for the people signed in
(`firmware`: offered, check, download), a fetched file kept in memory a
day per hardware and version, off with LOOP_FIRMWARE_RELAY=off. The app
asks on the day's first connection and on the band's screen, from Polar
or the instance as the person chose, never Polar in the instance's
place; an update runs only after asking, reads and keeps the band
first, and sets it up again with its figures and clock after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 20:52:53 +01:00
|
|
|
if (dataClassName == 'LoopFirmware') {
|
|
|
|
|
return deserialize<_i4qollgq.LoopFirmware>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'LoopFirmwareRefused') {
|
|
|
|
|
return deserialize<_ipcwdy0l.LoopFirmwareRefused>(data['data']);
|
|
|
|
|
}
|
2026-10-28 13:42:28 +01:00
|
|
|
if (dataClassName == 'MapSource') {
|
|
|
|
|
return deserialize<_ikw0qg8x.MapSource>(data['data']);
|
|
|
|
|
}
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
if (dataClassName == 'Plan') {
|
|
|
|
|
return deserialize<_i45prw73.Plan>(data['data']);
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (dataClassName == 'ContentKey') {
|
|
|
|
|
return deserialize<_imed3x5d.ContentKey>(data['data']);
|
|
|
|
|
}
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
if (dataClassName == 'Profile') {
|
|
|
|
|
return deserialize<_ia5j8fch.Profile>(data['data']);
|
|
|
|
|
}
|
2026-10-28 23:37:47 +01:00
|
|
|
if (dataClassName == 'PlannedRoute') {
|
|
|
|
|
return deserialize<_ikjgxraq.PlannedRoute>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'RouteHint') {
|
|
|
|
|
return deserialize<_if6974nb.RouteHint>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'RoutePoint') {
|
|
|
|
|
return deserialize<_isqtfus6.RoutePoint>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'RouteProfile') {
|
|
|
|
|
return deserialize<_ifl4j5ep.RouteProfile>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'RouteRefused') {
|
|
|
|
|
return deserialize<_ix4aepwk.RouteRefused>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'RouteStretch') {
|
|
|
|
|
return deserialize<_ic7f85bc.RouteStretch>(data['data']);
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (dataClassName == 'Workout') {
|
|
|
|
|
return deserialize<_i5b9b17z.Workout>(data['data']);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName == 'WorkoutSeries') {
|
|
|
|
|
return deserialize<_i92mefto.WorkoutSeries>(data['data']);
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
if (dataClassName.startsWith('halkyon_base.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(13);
|
|
|
|
|
return _ik8molhd.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName.startsWith('serverpod_auth_idp.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(19);
|
|
|
|
|
return _iais.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
if (dataClassName.startsWith('serverpod_offline_sync.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(23);
|
|
|
|
|
return _izehhkf5.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
if (dataClassName.startsWith('halkyon_oidc.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(13);
|
|
|
|
|
return _ipoqfiqr.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName.startsWith('serverpod_auth_core.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(20);
|
|
|
|
|
return _iacs.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
|
|
|
|
if (dataClassName.startsWith('serverpod.')) {
|
|
|
|
|
data['className'] = dataClassName.substring(10);
|
|
|
|
|
return _isp.Protocol().deserializeByClassName(data);
|
|
|
|
|
}
|
|
|
|
|
return super.deserializeByClassName(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _registerHostProtocols() {
|
|
|
|
|
_ik8molhd.Protocol().registerHostProtocol('halkyon_train', this);
|
|
|
|
|
_iais.Protocol().registerHostProtocol('halkyon_train', this);
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
_izehhkf5.Protocol().registerHostProtocol('halkyon_train', this);
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
_ipoqfiqr.Protocol().registerHostProtocol('halkyon_train', this);
|
|
|
|
|
_iacs.Protocol().registerHostProtocol('halkyon_train', this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
_is.Table? getTableForType(Type t) {
|
|
|
|
|
{
|
|
|
|
|
var table = _ik8molhd.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
var table = _iais.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
{
|
|
|
|
|
var table = _izehhkf5.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
{
|
|
|
|
|
var table = _ipoqfiqr.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
var table = _iacs.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
var table = _isp.Protocol().getTableForType(t);
|
|
|
|
|
if (table != null) {
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
switch (t) {
|
feat(train): the Loop's days read on the phone (ADR 0030, D37, D38)
The server gains Train's synced `day`, `day_series` (a day's heart
rate, deleted with its day) and `night`, each a sealed payload alone;
the migration `days` only adds their tables, indexes and foreign keys
(to their sync space, and a chunk's to its day, deferred), and the
person's deletion takes them with their spaces.
On Android, `LoopDays.kt` reads a band's days from a date to today,
framed by the SDK's sync start and stop, into lines of JSON, then
deletes on the band what the app kept up to the date it names, never
today; `LoopBridge` answers `readDays` with them, one band call at a
time, and `background` schedules `LoopWorker`, a WorkManager job every
four hours that appends the same lines to the person's
`loop_days.txt` while the app doesn't hold the band.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 19:53:04 +01:00
|
|
|
case _ikmv9f3y.Day:
|
|
|
|
|
return _ikmv9f3y.Day.t;
|
|
|
|
|
case _ir1ofny6.DaySeries:
|
|
|
|
|
return _ir1ofny6.DaySeries.t;
|
|
|
|
|
case _i17cgxdf.Night:
|
|
|
|
|
return _i17cgxdf.Night.t;
|
feat(train): interval plans, synced and sealed, with an editor and templates (ADR 0030, D42)
Train's server gains the synced `plan` table, a sealed payload alone;
the migration `plans` only adds it, its space index and the foreign key
to its sync space, and a person's deletion takes it with their space.
The app keeps plans as `plan` rows sealed for their row: a name, what
they are for, whether a step off target is told, and blocks of steps
repeated, each ending after a time, a distance or a lap tap and aiming
at a pace, speed, heart rate or zone, or the rower's split, watts or
stroke rate. Home's Plans tab lists them, a new one starts from nothing
or from a template (4×4 minutes, Tabata, 10×400 m), the editor changes
steps on a sheet, and a deletion asks first and can be undone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 22:35:22 +01:00
|
|
|
case _i45prw73.Plan:
|
|
|
|
|
return _i45prw73.Plan.t;
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
case _imed3x5d.ContentKey:
|
|
|
|
|
return _imed3x5d.ContentKey.t;
|
feat(train): the body's figures in a sealed, synced profile (ADR 0030, D33, D34)
A person's profile, one row per person (its ID derived from theirs),
sealed like a workout: sex, birth date, height, weight, resting and
maximum heart rate, VO2max, training background, typical day and sleep
goal, each optional. "Your body" in the account sheet edits it; the
Loop's setup is to reuse its form. The migration only adds the synced
table `profile`, deleted with the person's sync space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 18:02:04 +01:00
|
|
|
case _ia5j8fch.Profile:
|
|
|
|
|
return _ia5j8fch.Profile.t;
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
case _i5b9b17z.Workout:
|
|
|
|
|
return _i5b9b17z.Workout.t;
|
|
|
|
|
case _i92mefto.WorkoutSeries:
|
|
|
|
|
return _i92mefto.WorkoutSeries.t;
|
|
|
|
|
}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
List<_isp.TableDefinition> getTargetTableDefinitions() =>
|
|
|
|
|
targetTableDefinitions;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String getModuleName() => 'halkyon_train';
|
|
|
|
|
|
|
|
|
|
/// Maps any `Record`s known to this [Protocol] to their JSON representation
|
|
|
|
|
///
|
|
|
|
|
/// Throws in case the record type is not known.
|
|
|
|
|
///
|
|
|
|
|
/// This method will return `null` (only) for `null` inputs.
|
|
|
|
|
Map<String, dynamic>? mapRecordToJson(Record? record) {
|
|
|
|
|
if (record == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return _ik8molhd.Protocol().mapRecordToJson(record);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
try {
|
|
|
|
|
return _iais.Protocol().mapRecordToJson(record);
|
|
|
|
|
} catch (_) {}
|
feat(train): the sync engine and Train's first models (ADR 0030, D14)
Train's server syncs with serverpod_offline_sync as Learn's does, and
holds three synced tables, every payload sealed on the devices:
content_key (the person's data key, sealed to the account), workout (its
type, times, name and totals, changed when edited) and workout_series
(chunks of a workout's samples, written once). A person's space goes
with them, with everything in it.
The migration only adds: the three tables, each indexed on spaceId and
cascading with its space (a series also with its workout), and the sync
engine's own tables.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 11:13:04 +01:00
|
|
|
try {
|
|
|
|
|
return _izehhkf5.Protocol().mapRecordToJson(record);
|
|
|
|
|
} catch (_) {}
|
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.
Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-28 10:26:49 +01:00
|
|
|
try {
|
|
|
|
|
return _ipoqfiqr.Protocol().mapRecordToJson(record);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
try {
|
|
|
|
|
return _iacs.Protocol().mapRecordToJson(record);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
throw Exception('Unsupported record type ${record.runtimeType}');
|
|
|
|
|
}
|
|
|
|
|
}
|