- halkyon_ui: showAdaptiveSheet shows a dialog on Linux, macOS and Windows at every width (bottom sheets stay on phones and tablets below expanded windows), so Learn and Train follow; SheetScaffold takes a heading in the title's place and may leave out its close button. - Todo: the tabs are Inbox, Today, Upcoming, Browse, Today still opening; `serverpod start` launches the app on Linux, as it has no web build. - A task's sheet shows where the task is (its project, an icon once tasks are above it, and the tasks above it, each opening what it names) and a ⋮ menu in place of its close button: when it was added (its UUID v7), Duplicate with its sub-tasks, hiding its done sub-tasks (synced in its payload), Close in the pane, Delete after asking. The description's icon stays by its first line. - A row names its project and section at the end of its date's line; every menu entry has its icon. - Learn: a studyset's path starts at the Library tab's icon, named by its tooltip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
231 lines
17 KiB
Markdown
231 lines
17 KiB
Markdown
# 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). Built the same day: steps 1 to 3, Today and
|
||
Upcoming as lists, recurrence, reminders and the week strip (step 4), the task
|
||
pane on wide windows and search (D2).
|
||
- **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. As built (1 Nov): the pane is the frame's, beside whichever tab
|
||
is shown (`TaskPane` around the shell's tabs), 420 wide, the list keeping the rest; a
|
||
task tapped there opens in it, another replaces it, what was typed into the last
|
||
kept. Narrowed below expanded, the pane hides and tasks open in sheets again. Search is
|
||
a screen pushed onto the tab's own stack (back returns to the tab, a task found opens in
|
||
the pane where there is one): as typed, the projects whose name holds it and the tasks
|
||
every word is found in, in their title, description or labels' names, whatever the
|
||
case and the accents, open ones first by their date, done ones last; on the device
|
||
alone, which the screen says, as the instance reads no task (D5).
|
||
- **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.
|
||
As built (1 Nov, the developer's choice of `flutter_local_notifications` over
|
||
`awesome_notifications` and platform code of Halkyon's own): a task's reminders are
|
||
minutes before the time on its date, in its sealed payload, so every device of the
|
||
person's reminds; a task with a day alone has none until the automatic reminder (D4)
|
||
is a setting. Each device plans its reminders whenever the tasks change, comes to the
|
||
front, or every six hours: the next 48 within 30 days (iOS keeps 64 pending), a
|
||
recurring task's next time alone, each under an ID hashed from its task and offset so
|
||
that planning again replaces it. They are scheduled with the system on Android, iOS,
|
||
macOS and Windows, so they come while the app is closed, at their instant: a device
|
||
moved to another zone has them moved once the app is opened. Linux schedules none, so
|
||
there the running app shows them. Android brings them at the minute only where the
|
||
person allows exact alarms (`SCHEDULE_EXACT_ALARM`, off by default since Android 14),
|
||
else within a few minutes; the app doesn't send the person to that setting. The
|
||
permission to notify is asked when a task first gets a reminder; a notification's
|
||
text, the task's title, is hidden on a locked Android screen. Signing out, or
|
||
switching person, cancels the person's reminders on the device. Tapping one opens the
|
||
app, not yet its task.
|
||
- **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, under a row ID keyed by the account (Train's D57,
|
||
`keyedRowId`), not marked in its payload (as built, 1 Nov): every device makes the
|
||
same row, so two making it offline write one, and the instance can tell neither
|
||
which row is the Inbox nor whose it is; a payload's mark would let a moved row
|
||
claim to be it. 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.
|
||
As built (1 Nov): the rule is part of the task's date (`Repeat` in `TaskDate`, its
|
||
payload's `date.repeat`), so a task without a date doesn't recur and clearing the date
|
||
ends it; schema 1 holds it, as no task was written before. Done, a task counted from its
|
||
date moves to the rule's first day after both its date and today (an overdue one isn't
|
||
moved onto a day already past), counted from completion it moves the rule's units on
|
||
from today, on no particular weekday; its deadline moves as far, its time is kept, its
|
||
done sub-tasks open again, and when it was done is added to its `completions`. Undo
|
||
writes back every task that changed. The picker offers the rules that fit its day
|
||
(every day, every weekday, its weekday, its day or nth weekday of the month, its day of
|
||
the year) and a rule of the person's own; weeks start on Monday for every n weeks, until
|
||
the week's first day is a setting (D4).
|
||
- **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.
|
||
|
||
## Decisions after trying it (1 Nov 2026, the developer's)
|
||
|
||
The developer used the first build beside Todoist's and asked for these; where an
|
||
alternative was put to them, the choice is theirs.
|
||
|
||
- **D11 Sheets are dialogs on the desktop, at every width** (over the window's size
|
||
alone, and over Todo alone): halkyon_ui's `showAdaptiveSheet` shows a centred dialog
|
||
(Material's term) on Linux, macOS and Windows however narrow the window, and on phones
|
||
and tablets a bottom sheet below expanded windows as before, so Learn and Train follow.
|
||
A bottom sheet is a touch surface; rising from the foot of a desktop window it reads
|
||
as a phone's screen. In a narrow window the dialog keeps a smaller margin.
|
||
- **D12 The tabs are Inbox, Today, Upcoming, Browse; Today still opens** (amends D2's
|
||
order, not what opens).
|
||
- **D13 A task's sheet** names where the task is on top (its project, an icon once tasks
|
||
are above it, then each task above it; the project goes to its list, a task opens in
|
||
the sheet's place), as Learn's path through a studyset, and has a ⋮ menu in place of
|
||
its close button (`SheetScaffold`'s `heading` and `closeButton`): when it was added
|
||
(from its UUID v7, so the payload needn't say it), Duplicate (with its sub-tasks, right
|
||
after it), hiding or showing its done sub-tasks (`hideDoneSubTasks` in its payload,
|
||
so every device hides them, schema 1 as nothing is released), Close in the pane, and
|
||
Delete after asking. Copy link, View activity, Email to the task, Print and Open in a
|
||
new window wait for what they need (deep links, an activity log; mailing in is ruled
|
||
out by D5). The description's icon stays by its first line.
|
||
- **D14 Rows and menus**: a row shows its project and section ("Inbox / Errands" with the
|
||
project's icon) at the end of the line of its date, not beside the whole row; every
|
||
menu entry has its icon, a destructive one in the error colour (`MenuEntry`).
|
||
- **D15 Typed tokens in quick add** (amends D7's later step; recommended, the developer
|
||
asking whether the keyboard's language could choose): `#project`, `@label`,
|
||
`{deadline}`, `!!1` and `p1` to `p4`, `!30m`, dates and times (`9am`, `Mon 18:00`,
|
||
`ev Tue 7pm`) and recurrences (`every day`, `every 2 days`, `every second day`), each
|
||
highlighted as it is recognised and undone with a tap. The words are data, a table per
|
||
language (weekdays, months, *tomorrow*, *every*, *next*, times), read in the app's
|
||
language and English, so a new app language adds a table, not a parser. Not the
|
||
keyboard's language: Flutter tells nothing of it, Android doesn't reliably, and it
|
||
changes as the person switches keyboards while typing. Symbols hold in every language.
|
||
- **To come, in this order**: section headings that stay atop the list as it scrolls;
|
||
Upcoming's month opening inline beneath its strip (a handle dragged down, or the
|
||
month's name) with a way back to today beside it once another week shows, and its ⋮
|
||
with *Select tasks*; a row swiped left picks its date, swiped right selects it, a tap
|
||
then selecting more, with a bar of date, move, labels, priority and ⋮ (delete,
|
||
duplicate, complete, deadline); rows dragged into another order, and a little to the
|
||
right to become the sub-task of the row above, with Undo; the add button dragged onto
|
||
the list to add a task there, dropped back on its place (an ×) to give up, wherever
|
||
it shows; and quick add showing its fields as typing starts, its + opening the rest
|
||
(D15 with 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.
|