Commit graph halkyon/halkyon_learn_flutter/test/feature/studyset
Author SHA1 Message Date
bd6016ee1b
feat(app): choose a studyset's licence, its year and holder
The studyset sheet gains Licence, as before: "Help me choose" asks three
questions (keep any rights? commercial use? adaptations, alike?) and
recommends one of the six current Creative Commons licences or CC0, with
what it lets others do and a link to its terms; the licence field
suggests well-known licences and takes any other as typed, kept in one
spelling (`cc by-nc-sa 3.0 us` is `CC-BY-NC-SA-3.0-US`); the year sits at
its end (1000 to next year), the holder below. An older or ported
Creative Commons licence gets a note naming its current version. A new
sub-studyset starts with the licence of the one it is in.

Left out until publishing exists: the warnings about sub-studysets'
licences that a parent's doesn't cover. The licence shows for every
studyset, as there is no visibility yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:25 +02:00
b1ef9b9d3a
feat(app): a studyset's languages, and Chinese and Korean fonts
The studyset sheet gains Languages, as before: off until "Set
languages", then the language of the cards' fronts and of their backs
(22 languages, Chinese in its two scripts). A studyset without its own
takes those of the nearest studyset above it, both together; set for the
first time, they start from those it took. The payload held both fields
already.

They set its text: Noto Sans SC, TC and KR join Noto Sans JP (bundled,
never fetched; 34 MB more, fonts 42 MB in all, your call), and since
Han characters take the shapes of the first font that has them, every
screen inside a studyset (its screen, cards, notes, paths, studying it)
puts the font of its front's language first, else of its back's,
Japanese otherwise. Every studyset route goes through one helper now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:24 +02:00
b4fe6814ef
feat(app): the topics' foot the same with or without topics
Without sub-studysets, the side bar and the drawer showed New
sub-studyset in the invitation at the top and the bibliography alone at
the foot. Now New sub-studyset always sits at the foot's start, across
from the bibliography, once the topics are read (not while reordering);
the invitation keeps its words, without a button of its own (decided
2026-10-08).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:23 +02:00
fc2f55046b
feat(app): the bibliography at the foot of the side bar on the desktop
As on a phone's drawer: Bibliography at the end of the topics' foot,
across from New sub-studyset, instead of a button above the content. One
rail serves both now, the drawer and the side bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:23 +02:00
02efa2cdcf
feat(app): every empty studyset tab says how to add some, as Files did
Paths, flashcards and notes now show what Files showed when empty: an
icon for what the tab holds, "No … yet", and the menus that add some
("Add material → Create a path", "⋮ → Import content", …), through one
shared widget, EmptyListMessage. The icons are those the app uses for
each already: the route, the study screen's card stack, the note pen,
the folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:22 +02:00
04cbf00e3c
feat(app): notes in an order of one's own, with Reorder beside the tabs
With two notes or more, Reorder beside the tabs gives each note a grip
in place of its options, as paths have, until Done; e.g. to follow a
reading order. A note's position was stored already; the repository
now moves it (only that column changes, and it syncs).

The notes' bloc sits with the studyset's other lists now, above the
tabs, so the button knows how many there are. The position helpers the
paths kept to themselves are shared (core/sync/fractional_position.dart),
and so is the grip (ReorderGrip).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:22 +02:00
a35318a5a6
feat(app): More info for studysets, paths, notes and PDFs
Wherever the old menus offered it: the studyset's options, a path's
(in the list and in the runner), a note's, and a PDF's, all on the
shared sheet the cards use: when it was made (from its UUID v7 id) and
last changed where that is kept, its ids to copy (parent, studyset,
PDF), and for a PDF its file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:20 +02:00
37d18c35aa
feat(app): bibliography beside New sub-studyset in the drawer
On phones the topics' drawer ends on one row: New sub-studyset at the
start, the bibliography at the end. The rail takes what its foot ends
on, and keeps its foot while topics are rearranged or missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:19 +02:00
72b29c7e80
feat(app): the studyset screen laid out as before
The Study pill reads "Learn 12 flashcards" in the app bar and "Learn 12"
floating, "Study" with nothing due, and is off with nothing to study,
saying so. With a path not finished, a chevron beside it offers to start
or continue the path: a menu in the app bar, a Study mode sheet on
phones. The floating bar rises above the licence line, which takes two
lines at most.

Add material offers a path, flashcards, a note and a PDF, in that order;
imports moved to the options' Import content, which asks for a learning
path or an Anki deck. The options read Edit, Move, Study options, Import
content, Reset progress, Archive, Delete. The bibliography is an outlined
round button above the content, and on phones at the foot of the topics.

The tabs sit on a line of their own across the card, what the tab shown
offers beside them: the flashcards' filter and options, and on the paths
Reorder once there are two, which turns them into handles to drag. An
empty tab says so in its middle; tapping the open tab again returns to
its top, and pulling a tab down syncs. The breadcrumb's steps are plain
links between "›", the studyset itself bold. A long description is cut at
a fixed height, measured, with Read more opening it whole under the
studyset's title. A studyset that no longer exists leads back to the
library.

The topics: All topics on the primary colour, in view also while they
are rearranged; each topic with the study it invites in a round icon,
what it holds ("Empty" with nothing, files counted too), and its options
(now with Reset progress) behind "…" or a long press where the finger
is; New sub-studyset at the foot below a line, the list with its
scrollbar always shown. All topics opens Add material while nothing is
due.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:18 +02:00
aa69be47d1
feat(app): Home, the Library and the studyset sheets laid out as before
Home: plain cards on the surface, 16 dp apart, the goal first and the
Pomodoro timer after it in a new dashboard. The streak chip sits centred
in the app bar, and its sheet shows the days inside a big flame and the
week as marks over the weekdays' initials, with a word on today in a box.
The week names its day as "Thursday, Oct 8" and its days carry no
tooltip. The heatmap scrolls its weekdays with the weeks, outlines the
days to come, and names a day's activities with its date. The goal card
has a ring of 5 dp, a check once met and a pencil, and its sheet saves
across its width with Remove goal below. Arranging puts an eye on each
widget's corner and lifts the one picked up, with a tap the hand feels;
with every widget hidden, Customise brings arranging back. The stepper
moves to core, where the goal's sheet takes it too.

Library: the counts line includes files (PDFs); Refresh in the app bar
and pulling the list down sync now; a list that can't be read offers
Retry, which reads it again; the star keeps its square corners.

Studyset sheet: "Title *", the secondary title explained behind a
question mark, Inherit as a box beside the colour, and the description
in up to three lines with a chevron. Move sheet: round marks, the
studyset itself marked as current, the top level only while it is
inside another, archived studysets last under their heading, the
destination named in the footer, and a new studyset to move into, made
only once the move is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:18 +02:00
beb3a32c30
perf: keep a card's burial on its row rather than in the review log
Finding today's burials in the review log meant reading every review the
cards ever had, once more in the library's progress, the card list and
each round. card.buriedAt holds it instead, read with the cards those
screens read anyway. The sync engine merges a row's fields one by one, so
a burial and a move of the same card on two devices both stand (a sync
test checks it). The review log's bury mode is gone.

The migration adds one nullable column, on the server and on devices:
ALTER TABLE "card" ADD COLUMN "buriedAt" (timestamp without time zone on
the server, INTEGER in SQLite); existing rows read as not buried. Apps
built before it stop syncing until they are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:17 +02:00
9f1cfc1cdd
feat(app): ask what to study in a studyset with sub-studysets, and say when studying fails
- Studying a studyset that has sub-studysets and cards of its own
  first asks: everything (suggested) or this studyset only. Without
  either, it starts straight away. Alone, the back-first setting
  applies as usual and a turn of it is kept (a study button's focus
  may now leave the side to the settings).
- A session that can't be read says so, with Retry; a rating that
  can't be kept says so below the card, which stays to be rated
  again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:13 +02:00
b0aaecf115
feat(app): tag, edit, delete and swipe cards while studying, with tidier session sheets
- Above every studied card (but a matching one): the PDF it came from,
  its tags on one line ("+2 tags" for the rest) or "Add tags", and a
  menu to tag, edit or delete it; the session follows (an edited card
  shows as saved, a deleted one leaves the round).
- On phones, a front/back or vocabulary card swiped right is rated
  Good, left Bad; it tilts over the colour it heads for, and springs
  back when let go early.
- The session's settings: "Settings", a 48 dp Save that warns mid-round
  ("Heads up!"), steppers in an outlined box ("20 / day", "5 / 50"),
  the order in an outlined dropdown, the chosen tags with the rest
  behind "Filter by tags" / "+3 more".
- A studyset's study options: Save above Reset to defaults, the
  description small, reviews a day in steps of 25.
- One stepper look everywhere (study, Pomodoro profiles), its buttons
  "Decrease" and "Increase".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:12 +02:00
65976ae63a
feat(app): cite sources in APA, MLA or Chicago, and give studysets a bibliography
A source is now shared by learning paths and studysets (core/citation/):
authors as full names, year, title, what it is a part of, publisher,
ISBN, link, and a citation kept as a file brought it. formatCitation
cites it in APA 7, MLA 9 or Chicago 18: it inverts and abbreviates
names itself (particles, suffixes, names written as already cited stay),
sets a work of its own in italics and a part of something apart from
the whole, and writes a missing year as "n.d." in APA and Chicago.

- Settings: a Citations card per device, with a preview and a link to
  the style's own guide.
- Studysets: a Bibliography button opens the works cited, to read and,
  behind its pencil, to change (studyset schema 4).
- Steps: sources edited through the same sheet, opened from an
  "N sources" button in the runner (step schema 2; schema 1 reads on).
- Path import: steps' sources, text kept as written, the old format's
  "citation" read as what a source is part of.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-09 18:09:12 +02:00
ad28a5d8c2
refactor(app): studysets and settings are written in the newest schema
Decided 2026-10-07: the codecs write the newest schema every time, as
before, rather than the oldest one that holds what is there. An older
app loses sight of a studyset or of the settings once a newer one has
written them, until it is updated too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 12:28:05 +02:00
6ba229be2c
fix(app): study options saved as they came stay the defaults
A review found three edges of the study options:

- Saving a studyset's study options without changing anything wrote
  the defaults as its own, so it stopped following the person's new
  cards a day and was written in the newer schema older apps skip. It
  now keeps no options of its own.
- The library's badges read the person's new cards a day only when
  something else changed; a change to the setting now refreshes them.
- A person's random order shuffled the whole round, undoing the
  studyset's Reviews first or New first. The reviews and the new cards
  are now each shuffled, the order between them kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 12:28:03 +02:00
d42afb078a
feat(app): the library's options move, import into and set how a studyset is studied
A studyset's options in the library now offer, as on its own screen,
Edit, Move to…, Study options, Import from Anki, Archive and Delete.
Moving goes through the same sheet and says where the studyset went;
importing reads an Anki deck into it as Add material does.

Study options say how a studyset is studied, on every device: how many
new cards it starts a day (by default as the person's study settings
say), how many reviews it brings a day (no limit by default; cards in
their learning steps come anyway), and whether reviews or new cards
come first, or the new ones spread evenly among the reviews. They live
in the studyset's payload, schema 3; a studyset without them is still
written in schema 2, so that older apps, which skip a payload in a
newer schema whole, keep seeing every studyset nobody gave options.
Editing a studyset keeps its options, and Reset to defaults takes them
away.

The study queue takes each studyset's limits for its own cards and the
order of the studyset studied; a card's schedule now knows when it was
last reviewed, to count the reviews of the day. Sessions, the round's
end and the library's badges all count the same way, the badges now
with the person's own number of new cards a day too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 12:28:01 +02:00
55a0a7fdf0
feat(app): appearance, language and version in Settings, and Settings from Home
Settings gets a card for how this device shows the app: its appearance
(as the device, light or dark) and its language (the device's, or one
the app speaks, each named in its own language), side by side from
medium windows on, as two outlined dropdowns. Both stay on the device
rather than following the person: they are kept in
<app support>/device_settings.json (written to a temporary file and
renamed into place), read before the app shows, so its first frame
already looks as chosen, and a file that can't be read falls back to
what the device does. DeviceSettingsBloc sits above SessionGate's keyed
subtree, since the settings outlive every person, and both the plain
app and the person's app take their theme mode and locale from it.

At the bottom of Settings, the release and its build, read once at
start together with the build the server is asked about.

Home has a Settings cog beside the streak, with the sync trouble dot as
on Profile. The library's create button reads "Create studyset", and
Delete is red in every menu that offers it: the studyset's card, screen
and rail, and the composer's draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 12:27:56 +02:00
b96db731c1
feat: PDF sources: read, take notes and cards from pages, deadlines
Some checks failed
CI / check (push) Failing after 26s
CI / deploy (push) Has been skipped
PDFs live in a studyset's new Files tab: uploaded from Add material
(a file the studyset holds already asks first), each row with its
pages, cards, notes and size, how far it is read and its reading
deadline; renamed, given a deadline (a page or a chapter by a day),
marked as read, started afresh, deleted with a moment to undo.

The reader (/pdfs/:id) opens where reading left off: on wide windows
a rail of tools (search, fit, zoom, fullscreen), a page counter with
the chapter, and a sidebar; on phones a top bar, a page scrubber and
a sheet. The sidebar holds the PDF's flashcards, by page or chapter,
made from selected text or written there, each tied to its page and
tagged with its chapter; and the page's note, an ordinary note tied
to the page. Screenshots of a region become images that know their
page. Cards and screenshots lead back to their page; reading on past
a chapter with cards suggests studying it. Home shows the deadlines
running and to come, and a timeline of them.

Server: pdf_source rows (content in the payload, last writer wins)
and an append-only pdf_reading log, the furthest page derived from
it so devices never conflict. Files up to 50 MB for PDFs (20 MB for
others; a larger file that isn't a PDF is deleted once stored). A
deleted PDF takes its file's asset row along unless another PDF has
the same file. Card payload 4 and note payload 2 keep the page.

The reader draws with pdfrx (PDFium) behind PdfEngine in
Dependencies; widget tests use a fake, and
integration_test/pdf_engine_test.dart checks the real engine on a
device or the desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 14:40:08 +02:00
e4bfe3bdeb
feat(app): learning paths in the studyset screen: worked through and written
- A Paths tab before Flashcards and Notes: each path with its title and
  secondary title, dimmed once completed; Edit, Reset progress (with an
  undo) and Delete (with an undo) behind its options. The studyset opens
  on its paths while one is unfinished, on its flashcards otherwise.
- "Create a path" in Add material asks for the titles in a sheet and
  opens the new path to write; "Import a path (JSON)" reads a file in
  the import format, says what came in and how many blocks were left out
  (images and animations: the app never fetches from the web).
- The player: one step at a time at reading width, a bar of the steps
  above, Back and "Check answers" / "Continue" / "Finish" below. It
  opens at the first unfinished step; an exercise passes only once
  answered right, as often as it takes; after a check, only the
  learner's own picks are marked, with the author's feedback; sources
  behind a chip; a finish screen leading to the next path. Words and
  items are dragged into place, or tapped into place for phones.
- Writing: the path's screen (titles, description, tags, steps added by
  kind, reordered, deleted with an undo) and the step's (title, text
  with the format bar, its kind's exercise, feedback for right and
  wrong in the whole window, sources). The step saves on what it
  started from, as notes do, read-only while devices disagree until a
  version is kept.
- The library and the topics count paths; steps done count on the
  dashboard, for the streak and the activity.
- The rich-text screen for a studyset's description moves to core, for
  the steps' feedback too, and gains the format bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 09:25:08 +02:00
91f6376b93
feat: learning paths in studysets, synced, with progress and tags
Server: five synced tables, only additions (migration
20261002211346701-learning-paths):
- `learning_path`: its studyset, a fractional position, and its own
  content in the payload {title, secondaryTitle, description}, the last
  writer winning, as a studyset's;
- `learning_path_step`: its path, a position, and its kind ('info',
  'singleChoice', 'multipleChoice', 'dragAndDrop', 'fillInTheBlank'),
  fixed once created;
- `learning_path_step_revision`: a step's content as append-only
  revisions, as a card's: {title, body, exercise, rightFeedback,
  wrongFeedback, sources};
- `learning_path_answer`: an append-only log (right, wrong, continued,
  reset), as reviews are, so answers from two devices never conflict;
- `learning_path_tag`: one row per assignment, as `card_tag`.

App, `feature/learning_path`: the five exercises and their JSON, grading
that marks only the learner's own wrong picks, progress worked out from
the log (a step is done once answered right since its last fresh start),
`LearningPathRepository` (steps saved on what they started from, merged
per field) and `PathProgressRepository` (answers, resets with Undo, the
times steps were done). Paths share the person's tags; deleting a path
or a step can be undone for a moment, and a studyset's deletion takes
its paths. Tests: models, codecs, repositories, tags, the device
migration guard, and sync tests against the server (four).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 23:31:20 +02:00
502ce36a30
feat(app): notes in the studyset screen, written in the whole window
- A Notes tab beside Flashcards lists a studyset's notes in order: the
  title ("Untitled note" without one) and when each was last edited
  ("Edited 5 minutes ago", by the calendar from yesterday on, the date
  after a week); Edit and Delete behind its options. Deleting asks,
  then offers Undo for a moment (`NoteDeletions`). With none, the tab
  says so.
- "Create a note" in Add material opens a new note over the tabs; so
  does a note of the list. The note: a title, its tags once it exists,
  and its text at reading width; the app bar says how saving stands.
- `NoteEditorBloc` saves after 5 seconds without typing, when the app
  goes to the background, and when the note is closed (the bloc saves
  as it closes, so leaving never waits, and the write outlives the
  screen). Each save builds on the revisions the editor started from;
  another device's change takes over a note with nothing unsaved, and
  merges with one that has. A note changed differently on two devices
  is read-only until the person keeps a version of each field in
  conflict. A new note is created by its first save, never when left
  blank; a note deleted elsewhere says it no longer exists.
- The editor gains a format bar, docked below it while it has the
  focus (`RichContentEditor(formatBar: true)`): alignment, bold,
  italic, underline, superscript and subscript. It takes no focus, so
  the selection stays. Superscript and subscript are inline marks of
  their own, drawn by the font's raised and lowered glyphs in editors
  and read-only text alike.
- The library's and the topics' counts include notes (a studyset with
  what is inside it, as for cards).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 22:30:53 +02:00
f3b56f2c0c
feat: notes in studysets, synced and merged as cards are, with tags
Server: three synced tables, only additions (migration
20261002191122122-notes): `note` (its studyset, a fractional position),
`note_revision` (append-only, as a card's: parents, author, report,
the payload {schemaVersion, title, body}) and `note_tag` (one row per
assignment, never updated, as `card_tag`). Each indexes `spaceId`, and
everything goes with its space, studyset, note or tag. A migration of
synced tables locks app builds from before it out of syncing (ADR 0002,
rule 3): cheap now, before the first release.

App, `feature/note`:
- `NoteContent` is a title and rich text, merged apart (ADR 0003): a
  title changed on one device and the body on another both survive, a
  body changed on two is a conflict.
- `NoteRepository` creates a note at the end of its studyset, saves an
  edit on the revisions it started from (`saveEdit`, as the card
  composer now does), says when a note was last edited (its newest
  head's UUID v7), counts notes per studyset, and deletes a note's
  revisions blanked first (ADR 0002, rule 2).
- Notes share the person's tags: `setTagsOfNote`, and folding tags of
  one name or undoing a tag's deletion covers notes too.
- The delayed deletion with its Undo moves to `core/async`
  (`DelayedDeletions`): `StudysetDeletions` builds on it and now takes
  a studyset's notes too; `NoteDeletions` deletes a single note.

Tests: content, merge, payload, repository, deletions, tags, the device
migration guard, and sync tests against the server (four).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 21:53:56 +02:00
f922fceb47
feat(app): reset a studyset's progress, with an undo
The studyset's options offer Reset progress: after asking, every card
in it and in the studysets inside it starts afresh as new (a 'reset'
entry each), its history kept; an undo for a few seconds deletes those
entries again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 21:10:07 +02:00
46855ab251
feat(app): study sessions in rounds, by card type, cramming and reversed
A session studies a round of up to the settings' cards per round. In
spaced repetition a card leaves the round once it is a day or more away,
one rated lower comes back when due; cramming walks every card once,
round after round, its reviews moving no schedule, with going back and
on. Asking the back first (a toggle in the header, saved in the
settings) asks front/back and vocabulary cards their back, on their own
schedule. Each type studies its way: a word shows its reading and
meanings, a multiple-choice card is picked and checked, a matching card
sorted by dragging or tapping and checked; then the person rates (Bad,
OK, Good, Perfect, each with when the card comes back). Progress shows
as pills, or a tally bar while cramming; a round ends with how it went.
The session settings gain the mode, cards per round, new cards per day
and the order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 21:10:06 +02:00
31d87e8415
feat(app): the studyset screen with its topics, the tree's own screen
All checks were successful
CI / check (push) Successful in 5m28s
CI / deploy (push) Successful in 7s
A studyset's screen, as decided with the tree (2026-09-30/10-01):

- A breadcrumb from the library down to the studyset; its steps go up
  (replacing the location, so back leads further up), and steps that
  don't fit fold into a "…" listing them, nearest first.
- From expanded windows on, two columns: the description over the topics,
  the flashcards beside them, Study and Add material in the app bar. Below
  that, the topics are in a drawer, opened from a bar at the bottom that
  also holds Study and Add material and slides away while reading onwards;
  the back button stays where a drawer would have taken its place.
- The topics: "All topics" for the whole studyset, then the studysets
  inside it in their order, each with the study it invites, what it holds
  and its options (edit, delete), opening on top of this screen; a switch
  rearranges them by dragging; "New sub-studyset" creates one inside.
- Study covers the studyset with everything inside it ("Learn 12" while
  something is due). Add material offers flashcards for now.
- Options: Edit (the sheet, now with the description, edited in the whole
  window, and for a studyset inside another the colour of the one it is
  in, which a new one takes at first), Move to… (the tree and the top
  level, without the studyset itself and what is inside it), Archive or
  Unarchive with Undo, Delete with Undo, which closes the screen.
- The description cut short when long, the whole a tap away; the licence
  as the last line, linking to its text where that is known.

The list's bloc, scoped to a parent, lists its children, works out the
studyset's own progress, creates inside it, reorders and moves. The card
list leaves adding and studying to the screen. The inline editing of the
title and description, and its bloc, are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 15:14:57 +02:00
9c2960f49d
feat(app): study a studyset with everything inside it
Studying a studyset covers its subtree (decided with the tree, ADR 0004):
the studysets of its subtree in tree order, without archived studysets and
what is inside them (studiedIn); an archived studyset studies nothing. The
study queue takes the cards of each studyset as a group: due cards of every
studyset first, earliest first, then each studyset's new cards in tree
order, at most 20 started per studyset and local day, as before for one.

The library's cards count the same way, so a studyset whose cards sit in
its sub-studysets still invites a session, and the badge counts what the
session would study; an archived studyset, paused, still counts all it
holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 15:14:56 +02:00
7cc28c3cdc
feat(app): deletions to undo belong to the person, not to a screen
Decided 2026-10-01. Deleting a studyset from its own screen closes that
screen, which would have dropped a deletion kept there, and the studyset
would have quietly stayed. StudysetDeletions, opened with the person's data,
now keeps every deletion that can still be undone: a deleted studyset leaves
every list at once, and once the time to undo is over the whole subtree is
deleted, what is inside first, each studyset's cards blanked before it. It
closes first with the person's data: what still waits is dropped and stays,
what is under way finishes while the repositories are open. A failed
deletion shows what is left again.

The library's bloc hands its deletions to it and leaves out what it reports
as pending, instead of keeping timers of its own. Tests that follow a real
timer wait for the outcome (eventually) rather than for a fixed time a busy
machine might not keep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 15:14:56 +02:00
4fe7145b2f
feat(app): the studyset tree: children, subtrees, ancestors, moves
The model learns the tree (ADR 0004): a studyset's children in order (by
position, ties by id), its subtree in tree order (the studyset, then each
child with its own subtree), and its ancestors from the top level down,
for the breadcrumb. Studysets moved into each other on two devices make a
cycle; subtrees stop where it comes back, and climbing stops at the
studysets that show at the top level.

The repository creates a studyset inside a parent, after its siblings;
moves one into another studyset or to the top level, after the studysets
already there, writing only its parent, position and updatedAt; refuses a
move into the studyset itself or anything inside it; and reorders a
studyset among its siblings with a fractional index, as cards do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 15:14:55 +02:00
41f536bbe2
feat(app): deleting a studyset can be undone for a moment
Some checks failed
CI / check (push) Failing after 24s
CI / deploy (push) Has been skipped
Decided 2026-10-01. Deleting still asks first; once confirmed, the studyset
leaves the library at once and "Studyset deleted" offers Undo for three
seconds. Only when the time to undo is over (four seconds: the message's
three and a moment for a late tap) are its cards and then the studyset
blanked and deleted, since blanking can't be undone (ADR 0002, rule 2). A
deletion still waiting when the library closes (the app quits, the person
changes) is dropped, and the studyset stays.

The library's bloc keeps the waiting deletions; the event that ends the
time to undo is private, so no screen can skip it. AGENTS.md now describes
the library's shared widgets, the sheets (and why a sheet that asks before
discarding can't be swiped away), the order of studysets, and this Undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:47 +02:00
85c028fe3c
feat: studysets record when they last changed; the library lists them so
Decided 2026-10-01: after the starred ones, the library lists the most
recently changed studysets first. That needs a change date, so studyset
gains updatedAt (ADR 0004): when the studyset itself last changed, its
content, place, star or archive date, written by the device making the
change in the same update as the change; the last writer wins. Studysets
from before have none and come last, the newest of them first.

The branch's migration is still unreleased (not merged, not deployed), so it
is recreated rather than followed by a second one:
20261001064926911-tree-tags-assets replaces 20260930213716558 and differs
only by one line on the server, ALTER TABLE "studyset" ADD COLUMN
"updatedAt" timestamp without time zone (nullable, so existing rows keep
null and no data changes), the same column in the table devices rebuild,
and its version. The model's comment on the payload no longer lists schema
1's fields alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:46 +02:00
e2e088ce92
feat(app): the library: a grid of studyset cards, search and archive
The library tab becomes a grid of studyset cards (up to 400 dp wide, all
equally tall), starred ones first, and the archive as a section of its own
below, closed until opened and counting what rests there. It lists the top
level: studysets without a parent, those whose parent is gone, and those in
a cycle (ADR 0004). Each card's progress follows the studysets and every
change of a card, here or synced, and is worked out again after a study
session; a slow, older working out never overwrites a newer one.

- Search: a field below the app bar filters by title and secondary title,
  opens the archive while it finds something there, and says when nothing
  matches; closing it shows everything again.
- Creating and editing: a sheet with the title, the secondary title (typed
  in capitals, as every surface shows it) and the colour, picked from the
  palette's twenty, whose names are localised; a new studyset is cobalt,
  and an existing one keeps its colour, even one only a newer app version
  knows, until another is picked. Create waits for a title. Closing the
  sheet with changes asks first. It can't be swiped away: a bottom sheet
  swiped away closes without asking its PopScope (Flutter issue 152120,
  still open), so such sheets have no drag handle and close by their close
  button, the back gesture or a tap beside them, which all ask.
- Archiving doesn't ask, since nothing is lost; it offers to undo, as does
  unarchiving. Deleting asks, then deletes the studyset with its cards, each
  blanked first.
- The create button floats in compact windows and slides away while
  reading onwards; from medium windows on it sits in the app bar.

The old list and its title-only dialog are gone; the studyset screen's
tests create studysets through the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:46 +02:00
6dc1e863ea
feat(app): the library's studyset card
A studyset in the library: a bar in its colour, its title with the secondary
title as a kicker, what it holds, and beside the title the action it
invites inside a ring its mature cards fill, with a badge counting the due
cards (at most "99+", growing inwards so it never passes the card's edge).
The action is a review when reviews are due, studying when only new cards
are, and adding content otherwise, which opens the studyset: the card only
invites a session that has something to study. A long press stars it, as
does the star, which a hovering pointer reveals and which stays while
starred; the options edit, archive or unarchive, and delete it. An archived
studyset rests: dimmed, neutral, paused. Until its progress is known, a
muted bar stands in for "No content yet", which it might not be.

A starred star is amber, dark enough in the light theme to stand out from
the cards as a graphic must (3:1), a colour the theme extras now hold. It is
Material's filled star: Lucide has no filled one, and the fill is what says
the star is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:45 +02:00
b171b8e1d4
feat(app): studyset payload schema 2, stars and the archive
A studyset is now its content (StudysetContent, everything the synced payload
holds) and the columns that change on their own: where it sits (parent,
position), and when it was archived or starred. Like a card's content, the
payload's part is edited as one value; the columns are written one by one,
so starring or archiving on one device and editing on another all survive a
sync (ADR 0004).

The payload's schema 2 adds the secondary title, the colour (a palette name),
the licence (SPDX identifier or name, holder, year) and the languages of the
cards' two sides (BCP 47), each left out when not given. Schema 1 reads as
schema 2 without them, and a field of the wrong kind makes the payload
unreadable rather than half read. Older app versions skip schema 2 studysets,
which costs nothing before the first release; fields foreseeable for the
first release's features are in, so none of them needs a schema 3 later.

The repository stars, unstars, archives and unarchives (at clock time, in
UTC), writing only that column. A new sync test (run against a server)
checks that a star and an archive on one device and a rename on another all
arrive on both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:44 +02:00
c21ce6232b
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.

- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
  cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
  four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
  AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
  SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
  (core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
  its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
  a navigation bar below; choosing the open tab again returns to its first screen. A
  studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
  studyset deleted on another device, or an old link, says it no longer exists. A
  screen that deletes its own item keeps showing it while it closes, rather than
  flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
  MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
  by SessionGate inside the person's keyed subtree. Screens read the person's
  repositories wherever they are built, so the static route() helpers and the listener
  that closed every pushed screen on a change of person are gone: a change of person
  now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
  account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
  another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
  generated files are committed, left out of analysis and formatting, and written
  anew by `flutter pub get`, so CI's check of generated code also catches stale ones.

Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 09:59:44 +02:00
66886ef383
feat(app): flashcards on append-only revisions
A studyset's screen lists its cards (reorderable; a marker on cards changed on two devices) and opens a card's screen with an editor for each side. Saving appends a revision whose parents are all current heads. mergeRevisions merges several heads per side against their newest common ancestor, as decided: different sides merge automatically, the same side changed differently is a conflict. The card screen then shows every version of that side with "Keep this", and saving is possible once each conflicted side is settled, which resolves the conflict for every device. Nothing is merged silently inside a side.

CardRepository watches a studyset's cards, creates at the end and moves by fractional index (fractional_indexing_dart, checked against the reference algorithm's test vectors), and deletes by blanking every revision first (ADR 0002, rule 2). Deleting a studyset now deletes its cards the same way before the studyset goes. Every person gets a CardRepository whose revisions name them as author. RichContent gains plainText for the list's previews.

Tests: the merge (chains, different and same sides, conflicts, resolving, three heads, missing parents, arrival order), the payload, the repository against a fake gateway, both blocs, and the widgets, including that cancelling a delete keeps the card. The sync tests, which need the running server, cover merging and conflicts across two devices and blank revisions on the server; their device helper is now shared in test/sync/support. AGENTS.md describes the revision pattern, the fractional order, and two tooling traps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:38:17 +02:00
a98fabd0e6
refactor(app): the content payload codec moves to core
Cards are the second feature that stores content as a versioned UTF-8 JSON payload, so the shared part moves to core/content/payload.dart: encoding with a schemaVersion, decoding that refuses garbage and newer schemas, and the blanked redactedPayload (formerly redactedStudysetPayload). The studyset codec now builds on it and behaves as before; its tests pass unchanged apart from the rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:38:17 +02:00
312b18be5a
feat(app): sign in with GitHub and keep everything
The account dialog offers "Sign in with GitHub" to anonymous people where the instance has GitHub sign-in. It links the GitHub account to the person, who keeps all their studysets. If the account already belongs to another person (typically their account from another device), the app switches to that person right away when this device holds nothing, and otherwise asks first: switch and leave this device's studysets behind, or stay anonymous.

ServerpodAuthGateway uses Serverpod's GitHubSignInService for the browser part (PKCE), with the system browser and a loopback address on Linux and Windows (http://127.0.0.1:43823/github-callback) rather than an embedded webview, and app.halkyon.learn://github-callback elsewhere; Android gets the callback activity. After linking, the device's copy of the session gets the identified scope. A switch signs in through a client without a session, swaps sessions directly (never through "signed out", which would start another anonymous account), then ends the anonymous session left behind.

GitHubSignInBloc holds the flow; tests cover every branch (GitHub sign-in off, linked, cancelled on GitHub, switching right away, asking with staying anonymous as the refusal, switching once confirmed, a failure), and the widget tests the dialog. StudysetRepository gains all(), a one-off read: Stream.first on its stream never completes under the widget tests' fake clock, because the broadcast subscription's cancel future belongs to the root zone; AGENTS.md records it along with the GitHub pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:38:17 +02:00
3fc51057f1
test(app): nothing of one person reaches the next
Two guarantees of step 7 had no test. An open studyset closes when the person changes (here: the server revokes the session, and the app starts anew with another anonymous person), and the next person sees none of it. PersonScope shows the person's app once their data is open, says so when it can't be opened and then leaves no unhandled error behind (checked by removing the handler: the test fails), and closes the person's data when it goes away.

testDependencies takes an optional openPerson for this, and the fake auth gateway can revoke the session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:38:16 +02:00
9387e0a827
feat(app): studysets, offline first and synced on triggers
The first synced feature. Every person gets their own local database (<app support>/people/<authUserId>/sync.sqlite): PersonScope opens it inside SessionGate's per-person subtree through Dependencies.openPerson, together with its sync and the person's repositories (PersonDependencies), and closes it when the person changes. SessionGate then also closes pushed screens, which live above the person's subtree; a screen's route() hands its bloc the repository it needs.

SyncStudysetGateway reads and writes the studyset table through the sync session, names the payload column in every update and asks for a sync after every write. StudysetRepository encodes and decodes the payload (studyset_payload.dart: UTF-8 JSON with a schema version; a newer schema or garbage is skipped with a log), re-reads after its own writes and after merges, and blanks the payload before deleting, since the engine keeps deleted rows until the space is deleted (ADR 0002, rule 2). A failed read reaches the list as an error, and the next change reads again. SyncService syncs on triggers: at start, 2 s after the last local write, on resume and every 5 minutes, one sync at a time, and lets a running sync finish before the database closes.

Screens: the list, now the home screen in place of the editor playground, creates studysets through a dialog; a studyset's screen edits its title and description, saves on request and asks before deleting.

Tests: the payload codec, the repository against a fake gateway (including a failed read and rows from a newer app), the sync triggers under fakeAsync, both blocs' sequences, and the screens, including that cancelling the delete keeps the studyset. The session tests now settle until the person's data is open. AGENTS.md describes the per-person graph, synced data and the envelope as it now is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:38:14 +02:00