docs(todo): ADR 0033, what Todo does and how it looks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-01 00:50:52 +01:00
commit 3133658631
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 145 additions and 1 deletions

View file

@ -2,7 +2,7 @@
What holds for every app and package of this repository is in the [root AGENTS.md](../../AGENTS.md); it applies here in full, and Learn's patterns (`apps/learn/AGENTS.md`) wherever Todo needs the same thing.
- **A shell on the shared packages** (decided 2026-10-19): signing in, the instances, the account and the look work; nothing of a person's is kept or synced yet, which waits for Todo's first model (the developer decides what it keeps and syncs). Packages: `halkyon_todo_server`, `halkyon_todo_client` (generated, never edited), `halkyon_todo_flutter`; app ID `app.halkyon.todo` on every platform; no web build. Licences as Learn's (server SSPL, client and app AGPL; `LICENSE.md`).
- **A shell on the shared packages** (decided 2026-10-19; what Todo is to do and how it looks: ADR 0033, decided 2026-11-01): signing in, the instances, the account and the look work; nothing of a person's is kept or synced yet, which waits for Todo's first model (the developer decides what it keeps and syncs). Packages: `halkyon_todo_server`, `halkyon_todo_client` (generated, never edited), `halkyon_todo_flutter`; app ID `app.halkyon.todo` on every platform; no web build. Licences as Learn's (server SSPL, client and app AGPL; `LICENSE.md`).
- **The server** includes `halkyon_base` (ADR 0014) and configures it in `server.dart` (`todoBase()`, `lib/src/todo_base.dart`): its basics say it serves `todo` (never change it once apps use it), sign-in mails name Halkyon Todo, the source is this repository, `minimumAppBuild` 1, no data of its own to delete with a person yet (add a `deletePersonData` hook with Todo's first table). Authentication through the module's builders: a password, SimpleLogin on the register, email codes proving an address, server-side sessions; no anonymous accounts (ADR 0020, D25), nor GitHub (D36). **Accounts** (ADR 0016): Todo holds its own register when it runs alone, and is a member of the instance's register (Learn's server on learn.halkyon.eu) where the operator sets `ACCOUNT_REGISTER_URL` (`todoBase(accounts: AccountsMode.fromEnvironment())`) with the instance's shared `INSTANCE_SECRET` (`instanceSecret`); then sign-in, handles and profiles are the register's, the email settings are the register's alone, and an identified person's deletion goes to the register first. `addInstanceRoutes` serves the calls between the services on its web server, and the server doesn't start without the secret where it needs it. `schedulePruning` (`lib/src/pruning.dart`) keeps the module's pruning scheduled, one entry each however often the server starts (and cancels the one under the pruning's name before ADR 0020, D25): the anonymous accounts left from before daily, sign-up attempts and codes hourly, as the register the deletions its members haven't confirmed hourly, as a member the people the register no longer knows daily (each a no-op in the other mode). Session logs are never persisted, in any run mode. Migrations (2026-10-19): the initial one (Serverpod's tables, its auth modules' and the module's `email_identity`, `email_code_request`), `profiles` (`public_profile`) and `deletion-notices` (`account_deletion_notice`), nothing of Todo's own. Tests (`test/integration/todo_server_test.dart`) call the module's endpoints through the server's own dispatch (`halkyon_base.<endpoint>`), since the generated test tools expose the project's endpoints alone.
- **The app** is halkyon_app's `InstanceGate` and `SessionGate` with Todo's frame (`todoFrame`: "Todo" under "Halkyon", its icon, `todo.example.org`, its own sentence) and `ServerpodInstanceChecker(app: 'todo')`; `TodoDependencies` (`AppDependencies<TodoPerson>`) on one instance, `TodoPerson` (empty) per person; a person's app with an empty home and the account sheet: halkyon_app's `AccountTiles` (whose account on which instance, Switch account, Sign out, Delete account, which asks first) in Todo's words, and halkyon_app's About (`showAppAbout`: the source of what the instance runs, from `TodoDependencies.sourceCodeUrl`, and the licence list). Texts through gen-l10n in British English (the template), German (informal du), an empty `en_US` and `en_GB` (`test/l10n/arb_files_test.dart` keeps them in step). The look follows the system's theme mode and language until Todo has settings. Countries in where an instance keeps its data are named by halkyon_app's CLDR names (`AssetCountryNames`), its language picked by halkyon_app's `resolveAppLocale`. Widget tests start Todo's whole tree with halkyon_app's fakes (`test/app/todo_app_test.dart`).
- **The icon** is Lucide's `list-todo` fading from cobalt through pacific into fern (ADR 0031), drawn with the other apps' by the repository's `tool/app_icons.py` (`python3 -I tool/app_icons.py todo` from the root): `assets/icon/halkyon_todo_icon.png`, Lucide's ISC licence beside it, and every platform's launcher icon through flutter_launcher_icons. `registerLucideLicense` puts the notice in the licence list.

View file

@ -0,0 +1,144 @@
# ADR 0033: Halkyon Todo, what it does and how it looks
- **Status:** decided 1 Nov 2026, by the developer (D1–D10 below, the agent's
recommendations taken as a whole). Nothing of it is built yet: Todo is still the shell
of 19 Oct 2026 (`apps/todo/AGENTS.md`), an empty home and an account sheet.
- **Numbering:** 0025–0029 are held (ADR 0030's note), 0032 is Jellyfin's; this is the
next free number.
- **Builds on:** ADR 0030, D7, D12, D14, D15 and D60 (Train's sealed rows, its shell, its
frame and settings, which Todo takes); ADR 0017 (the account's keys); ADR 0031 (Todo's
icon); Train's D61 (What's new).
## Context
On 1 Nov 2026 the developer asked for work on Todo to start from 68 screenshots of
Todoist's Android app (31 Oct 2026, `~/Downloads/todoist-screenshots-mobile/`), "following
the same foundational reasoning of Halkyon Learn and Halkyon Train": when the bar and when
the rail, a wash of strawberry, settings grouped as theirs. What the screenshots show:
- **Five tabs** (Inbox, Today, Search, Upcoming, Browse), a floating add button above the
bar, the bar's items and the button's side set by the person; Browse holds the
projects, Reporting, templates and the cog to Settings.
- **A task** has a title, a description, a **date** (optional time and **duration**,
"06:30–07:00") and, apart from it, a **deadline** (date only, its own icon, neutral
colour; a task may have a deadline and no date), a priority (P1–P4), labels, reminders
(relative to the time, automatic 30 minutes before a timed task; by location in the paid
plans), a recurrence (completing it moves it on; its sub-tasks reset), sub-tasks
(counted "0/5" on its row), comments with attachments. Rows colour the date by meaning
(overdue red, tomorrow orange, this week purple).
- **Projects** have sections (name, description, count), seen as a list, as a **board** (a
column a section, paged) or as a **calendar** (a week strip opening into the month, an
agenda below). **Today** puts Overdue first, with Reschedule for all of them;
**Upcoming** is an agenda from today on, or a **3-day** grid of hours where a long press
picks 30 minutes. Every view has its own **Display** sheet: layout, completed tasks,
grouping, sorting, filters by date, deadline, priority and label.
- **The task sheet** shows a row for each field set and a chip for each one not, the
sub-tasks, and a comment field at its foot; **quick add** is a card above the keyboard
with chips (date, deadline, priority, reminders, project), dates recognised in what is
typed.
- **Productivity**: a daily and weekly goal, streaks, karma, days off and a vacation mode;
**Reporting**, the completed tasks by day.
- **Settings**: Account, General (home view, smart dates, reset sub-tasks, the week's
first day, how "next week" and "weekend" are read, swipe actions), Theme, Navigation
bar, Quick Add, Productivity, Reminders, Notifications (a morning overview and an evening
review), About, What's new.
## Decisions (1 Nov 2026, the developer's)
- **D1 Todo's frame is Train's (ADR 0030, D60)**: go_router with a `StatefulShellRoute`,
every tab keeping its own stack; halkyon_ui's `NavigationFrame`, a bar floating above the
foot below medium windows and a rail with Todo's icon on top from medium on, a tick on
every choice; Todo's account sheet goes. Over a frame of Todo's own, which would make the
three apps behave differently on the same window.
- **D2 Four tabs: Today, Upcoming, Inbox, Browse** (recommended, over Todoist's five and over
a bar the person arranges). Today opens. Browse holds the projects, the labels and,
later, saved filters, with the cog to Settings atop it as Train's Home and Profile carry
theirs. **Search** is an icon in every tab's app bar, not a tab, as neither Learn nor
Train gives search a tab. **Adding a task** is a floating button above the bar
(Train's `TabScreen` places it clear of the bar), on the rail's screen from medium
windows on. From expanded windows on, a task opens in a pane beside its list, as Train's
Maps opens a route's map beside the list (Material's list–detail layout), and in a
bottom sheet below. Arranging the bar may come later; Search may become a tab if it
proves to be used as one.
- **D3 Every screen washes down from the top in strawberry** (the developer's):
`TopGradient` under the app's `ScreenColor`, `PaletteColor.strawberry`, as Learn's in
cobalt and Train's in carrot; buttons and links stay cobalt.
- **D4 Settings as Learn's and Train's** (the developer's): a screen over the tabs,
grouped in `SectionCard`s. Sync's card first (as Learn's), the operator's card for
whoever operates the instance, then Todo's own groups (*Tasks*: the week's first day
from the language unless set, resetting sub-tasks; *Reminders*: the automatic reminder
before a timed task, snoozing), the account near the foot (halkyon_app's
`AccountTiles`, `SimpleLoginSection`, `AccountSecuritySection`), What's new and About
Halkyon Todo last, the version small below them. Groups come as their settings are
built; none is shown empty.
- **D5 Todo's rows are sealed, always, as Train's (ADR 0030, D7, D15)**, synced with
`serverpod_offline_sync`: every payload ADR 0017's sealed content under the person's
data key (`content_key`, halkyon_app's `PayloadEnvelope`), nothing readable in a column,
not a title nor a date. Recommended over readable rows (ADR 0002's default), which would
let the server send reminders by email and a morning overview, and would make shared
projects simpler: what a person plans to do is theirs, the server needs to read none of
it to sync it, and Todo's rows can't be unsealed later without the person's devices. So
**reminders are scheduled on the device**, as local notifications on each device that
holds the account's keys; there is no email reminder and no overview the server writes.
A project shared later is sealed under a key its members share, which is that step's to
decide.
- **D6 Four tables: `project`, `section`, `task`, `label`** (recommended, as Train keeps a
workout and its series apart, D14): one row each, changed when edited, the last writer
winning, every payload JSON with a `schemaVersion`, UUID v7 ids. Inbox is a project the
app makes once per person, marked so in its payload. A **sub-task** is a task naming its
parent. A task's payload: title, description, its project and section, its parent, its
place among its siblings, a **date** (a day, or a day and a time with an optional
duration, the time as the person's wall clock with its time zone), a **deadline** (a
day), a priority (1–4), its labels' ids, a recurrence (D7), its reminders, when it was
completed. Completing a recurring task moves its date on and writes the occurrence done
into its payload's history, so Reporting can count it. Comments and attachments are a
later step (attachments need sealed files, which no Halkyon app has yet).
- **D7 A recurrence is a structured rule, set through a picker** (recommended, over
parsing what is typed): every n days, weeks (on which weekdays), months (on which day,
or the nth weekday), years, from the date or from completion, until a day or for n
times. Dates and tokens typed into quick add ("tomorrow 6am", "every Monday", `#project`,
`p1`), in English and German, are a later step, built on the same rule.
- **D8 The first version** (recommended): Inbox and projects with sections, shown as a
list; sub-tasks; Today (Overdue first, rescheduling them together, then today's); Upcoming
as an agenda with the week strip opening into the month; the task sheet (rows for the
fields set, chips for the others, the sub-tasks); quick add; labels; completed tasks shown
or hidden per view; reminders on the device. **Later**, each put to the developer when
its turn comes: the board and calendar layouts, Upcoming's 3-day grid, each view's
Display sheet (grouping, sorting, filters), saved filters, Reporting and goals (karma
not before the developer asks for it), location reminders, comments and attachments,
typed dates, sharing.
- **D9 Colours that mean something come from halkyon_ui's palette** (recommended): a
project's colour is picked from `PaletteColor`, with its localised name, as Learn's
colours are; priorities and dates take palette colours chosen not to blur against the
strawberry wash, so overdue and P1 are **cerise**, not strawberry, P2 carrot, P3
cobalt, P4 none; tomorrow and this week as Todoist's meanings go, in palette colours.
Each is checked against the wash in light and dark before it is kept.
- **D10 The icon stays as ADR 0031 drew it** (cobalt through pacific into fern), though
the wash is strawberry: Learn's icon ends in strawberry and washes in cobalt, so an
app's wash and its icon already differ, and Todo's and Meditate's icons are kept
related (ADR 0031, D1). The agent made no recommendation; this is the default the
decision of 1 Nov left in place, to be asked again if the screens look wrong beside it.
## Steps
1. **The frame**: go_router and `NavigationFrame` with the four tabs (empty screens that
say what comes), the strawberry wash, the cog, Settings as D4 with what exists today
(the account, What's new, About, the version); the account sheet removed.
2. **The first synced model**: `content_key` and `project` (Inbox made once), its gateway
and repository on Learn's and Train's pattern, the server's access rules each with a
test asserting denial; walked through with the developer as the first of Todo's.
3. **Tasks**: `section`, `task`, `label`; Inbox and a project's list; the task sheet; quick
add.
4. **Today and Upcoming**, recurrence (D7), reminders on the device.
5. Todo's first release (0.1.0), its *What's new* drafted by `tool/release.dart`.
## Consequences
- No email reminder, morning overview or evening review: the server can't read what is
due. A device without the account's keys neither reads nor writes Todo's rows, as in
Train.
- Todo's server holds no logic over tasks; what Today, Upcoming and Reporting show is
worked out on the device, over every task the person has. That is fine for thousands of
tasks; a person with far more would need the device to keep an index of its own.
- Sharing a project is harder than with readable rows (a key per shared project, its
members' keys wrapping it); D5 accepts that for now.