feat(todo): comments and descriptions in the editor (ADR 0033, D38)

Comments and the descriptions of tasks, projects, sections and filters
become the editor's document on halkyon_editor; names and titles stay
text. Rows written as text read as notes and become documents when next
saved, the server seeing only sealed bytes, so there is no migration.

Sections' descriptions can now be written, and a project's shows above
its tasks. Android answers whether a link can open only for what is
declared, so Todo declares http and https.

Whats-New: Comments and descriptions take rich text: headings, lists, checklists, links and more.
Whats-New-De: Kommentare und Beschreibungen können jetzt formatiert werden: Überschriften, Listen, Checklisten, Links und mehr.

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-06 21:36:05 +01:00
commit a25cca12b4
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
3 changed files with 645 additions and 282 deletions

View file

@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_todo_flutter/src/core/note/note.dart';
import 'package:halkyon_todo_flutter/src/core/sealed/json_payload.dart';
import 'package:halkyon_todo_flutter/src/feature/label/data/label_payload.dart';
import 'package:halkyon_todo_flutter/src/feature/label/model/label.dart';
import 'package:halkyon_todo_flutter/src/feature/section/data/section_payload.dart';
@ -24,7 +26,7 @@ void main() {
final full = TaskContent(
title: 'Pilates',
projectId: 'p',
description: 'Bring the mat',
description: noteOf('Bring the mat'),
sectionId: 's',
parentId: 't',
position: 3,
@ -65,7 +67,7 @@ void main() {
'schemaVersion': 1,
'title': 'Pilates',
'projectId': 'p',
'description': 'Bring the mat',
'description': noteOf('Bring the mat').toJson(),
'sectionId': 's',
'parentId': 't',
'position': 3.0,
@ -259,11 +261,21 @@ void main() {
});
});
test('a description written as text before D38 reads as a note', () {
final payload = encodeJson(1, {
'title': 'Yoga',
'projectId': 'p',
'description': 'Bring the mat',
});
expect(decodeTask(payload)?.description.text, 'Bring the mat');
});
test('a section and a label read back as they were written', () {
const section = SectionContent(
final section = SectionContent(
name: 'Everyday life',
projectId: 'p',
description: 'About everyday life',
description: noteOf('About everyday life'),
position: 2,
);
expect(decodeSection(encodeSection(section)), section);

View file

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:clock/clock.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_todo_flutter/src/core/note/note.dart';
import 'package:halkyon_todo_flutter/src/core/privacy/todo_keys.dart';
import 'package:halkyon_todo_flutter/src/feature/comment/data/comment_repository.dart';
import 'package:halkyon_todo_flutter/src/feature/comment/model/comment.dart';
@ -522,7 +523,7 @@ void main() {
final comments = CommentRepository(rows, await keysOf(alice));
final comment = CommentContent(
taskId: 't1',
text: 'Ask the vet',
text: noteOf('Ask the vet'),
writtenAt: DateTime.utc(2026, 11, 6),
);
await comments.add(comment);
@ -558,7 +559,7 @@ void main() {
final at = DateTime.utc(2026, 11, 6);
for (final id in [parent, sub, other]) {
await comments.add(
CommentContent(taskId: id, text: id, writtenAt: at),
CommentContent(taskId: id, text: noteOf(id), writtenAt: at),
);
}

View file

@ -1,4 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_todo_flutter/src/core/note/note.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task_search.dart';
import 'package:halkyon_todo_flutter/src/feature/task/model/task_views.dart';
@ -19,7 +20,7 @@ void main() {
content: TaskContent(
title: title,
projectId: 'p',
description: description,
description: noteOf(description),
labelIds: labels,
date: day == null ? null : TaskDate(day),
completedAt: done ? DateTime(2026, 11, 2) : null,