feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
|
|
import 'package:path/path.dart' as p;
|
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
|
|
import '../../tool/src/release/apps.dart';
|
|
|
|
|
import '../../tool/src/release/version.dart';
|
|
|
|
|
import '../../tool/src/release/whats_new.dart';
|
|
|
|
|
import 'fixtures.dart';
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
test('the drafts list the features, then the fixes, without types, scopes '
|
|
|
|
|
'and closing references, as sentences; German marked to translate', () {
|
build(release): what's new drafted from the commits' Whats-New trailers, other apps' commits left out
The drafts came from the commits' subjects, which are written for
developers: each of the releases of 4 Nov 2026 (Todo 0.2.0, Train 0.3.0,
Learn 0.24.0) listed every commit since the last, Meditate's among them
where it touched a shared package, more than twice F-Droid's 500
characters, the German all still to translate, and each had to be written
again whole.
A commit people would notice now says so in its message's trailers,
`Whats-New: <sentence>` once a change and `Whats-New-De: <Satz>` in the
same order, and the draft is those lines alone, the features' first, a
missing translation marked TRANSLATE:. Only where no commit of the
release carries one does it fall back to the subjects. A commit whose
scope names apps (feat(todo,meditate): ...) counts for those alone, any
other for every app it touches. Over a running next.<language>.txt per
app, which parallel branches conflict on at every merge, and over an
assistant drafting at release time (a key, the commits sent to a
provider, a different text each run). ADR 0030, D61.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-04 20:19:12 +01:00
|
|
|
final drafts = draftWhatsNew(
|
|
|
|
|
const [
|
|
|
|
|
Commit(hash: 'a', subject: 'fix(sync): no lost rating'),
|
|
|
|
|
Commit(hash: 'b', subject: 'docs: why'),
|
|
|
|
|
Commit(
|
|
|
|
|
hash: 'c',
|
|
|
|
|
subject: 'feat(train): tabs folding into the title (ADR 0030, D60)',
|
|
|
|
|
),
|
|
|
|
|
Commit(hash: 'd', subject: 'perf!: faster library.'),
|
|
|
|
|
Commit(hash: 'e', subject: 'chore(release): notes-v0.18.0'),
|
|
|
|
|
],
|
|
|
|
|
first: false,
|
|
|
|
|
app: 'notes',
|
|
|
|
|
apps: _apps,
|
|
|
|
|
);
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
|
|
|
|
|
expect(drafts['en'], '''
|
|
|
|
|
- Tabs folding into the title.
|
|
|
|
|
- No lost rating.
|
|
|
|
|
- Faster library.
|
|
|
|
|
''');
|
|
|
|
|
expect(drafts['de'], '''
|
|
|
|
|
- TRANSLATE: Tabs folding into the title.
|
|
|
|
|
- TRANSLATE: No lost rating.
|
|
|
|
|
- TRANSLATE: Faster library.
|
|
|
|
|
''');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('the first release says so', () {
|
|
|
|
|
expect(
|
build(release): what's new drafted from the commits' Whats-New trailers, other apps' commits left out
The drafts came from the commits' subjects, which are written for
developers: each of the releases of 4 Nov 2026 (Todo 0.2.0, Train 0.3.0,
Learn 0.24.0) listed every commit since the last, Meditate's among them
where it touched a shared package, more than twice F-Droid's 500
characters, the German all still to translate, and each had to be written
again whole.
A commit people would notice now says so in its message's trailers,
`Whats-New: <sentence>` once a change and `Whats-New-De: <Satz>` in the
same order, and the draft is those lines alone, the features' first, a
missing translation marked TRANSLATE:. Only where no commit of the
release carries one does it fall back to the subjects. A commit whose
scope names apps (feat(todo,meditate): ...) counts for those alone, any
other for every app it touches. Over a running next.<language>.txt per
app, which parallel branches conflict on at every merge, and over an
assistant drafting at release time (a key, the commits sent to a
provider, a different text each run). ADR 0030, D61.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-04 20:19:12 +01:00
|
|
|
draftWhatsNew(const [], first: true, app: 'notes', apps: _apps)['en'],
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
'- The first release.\n',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
build(release): what's new drafted from the commits' Whats-New trailers, other apps' commits left out
The drafts came from the commits' subjects, which are written for
developers: each of the releases of 4 Nov 2026 (Todo 0.2.0, Train 0.3.0,
Learn 0.24.0) listed every commit since the last, Meditate's among them
where it touched a shared package, more than twice F-Droid's 500
characters, the German all still to translate, and each had to be written
again whole.
A commit people would notice now says so in its message's trailers,
`Whats-New: <sentence>` once a change and `Whats-New-De: <Satz>` in the
same order, and the draft is those lines alone, the features' first, a
missing translation marked TRANSLATE:. Only where no commit of the
release carries one does it fall back to the subjects. A commit whose
scope names apps (feat(todo,meditate): ...) counts for those alone, any
other for every app it touches. Over a running next.<language>.txt per
app, which parallel branches conflict on at every merge, and over an
assistant drafting at release time (a key, the commits sent to a
provider, a different text each run). ADR 0030, D61.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-04 20:19:12 +01:00
|
|
|
test("commits scoped to other apps don't count; a shared package's do", () {
|
|
|
|
|
final drafts = draftWhatsNew(
|
|
|
|
|
const [
|
|
|
|
|
Commit(hash: 'a', subject: 'feat(diary): the first diary'),
|
|
|
|
|
Commit(hash: 'b', subject: 'feat(notes,diary): a lock on both'),
|
|
|
|
|
Commit(hash: 'c', subject: 'feat(app): a profile everywhere'),
|
|
|
|
|
Commit(hash: 'd', subject: 'fix(deploy): the image builds'),
|
|
|
|
|
],
|
|
|
|
|
first: false,
|
|
|
|
|
app: 'notes',
|
|
|
|
|
apps: _apps,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(drafts['en'], '''
|
|
|
|
|
- A lock on both.
|
|
|
|
|
- A profile everywhere.
|
|
|
|
|
- The image builds.
|
|
|
|
|
''');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('the trailers', () {
|
|
|
|
|
const commits = [
|
|
|
|
|
Commit(
|
|
|
|
|
hash: 'a',
|
|
|
|
|
subject: 'fix(notes): the sync no longer loses a rating',
|
|
|
|
|
body:
|
|
|
|
|
'Why it did.\n\n'
|
|
|
|
|
'Whats-New: Ratings are kept when syncing.\n'
|
|
|
|
|
'Whats-New-De: Bewertungen bleiben beim Synchronisieren.\n'
|
|
|
|
|
'Signed-off-by: A <a@example.org>\n',
|
|
|
|
|
),
|
|
|
|
|
Commit(hash: 'b', subject: 'feat(notes): a refactor nobody sees'),
|
|
|
|
|
Commit(
|
|
|
|
|
hash: 'c',
|
|
|
|
|
subject: 'feat(app): tabs folding into the title (ADR 0030, D60)',
|
|
|
|
|
body:
|
|
|
|
|
'Whats-New: Tabs fold into the title.\n'
|
|
|
|
|
'Whats-New: Search: in every list.\n'
|
|
|
|
|
'Whats-New-De: Tabs falten sich in den Titel.\n',
|
|
|
|
|
),
|
|
|
|
|
Commit(
|
|
|
|
|
hash: 'd',
|
|
|
|
|
subject: 'feat(diary): a diary',
|
|
|
|
|
body: 'Whats-New: A diary.\nWhats-New-De: Ein Tagebuch.\n',
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
test("are what's new where a commit has them: the features' first, "
|
|
|
|
|
'commits without them left out, those of other apps too', () {
|
|
|
|
|
final drafts = draftWhatsNew(
|
|
|
|
|
commits,
|
|
|
|
|
first: false,
|
|
|
|
|
app: 'notes',
|
|
|
|
|
apps: _apps,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(drafts['en'], '''
|
|
|
|
|
- Tabs fold into the title.
|
|
|
|
|
- Search: in every list.
|
|
|
|
|
- Ratings are kept when syncing.
|
|
|
|
|
''');
|
|
|
|
|
expect(drafts['de'], '''
|
|
|
|
|
- Tabs falten sich in den Titel.
|
|
|
|
|
- TRANSLATE: Search: in every list.
|
|
|
|
|
- Bewertungen bleiben beim Synchronisieren.
|
|
|
|
|
''');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('are left alone in the first release', () {
|
|
|
|
|
expect(
|
|
|
|
|
draftWhatsNew(commits, first: true, app: 'notes', apps: _apps)['de'],
|
|
|
|
|
'- TRANSLATE: The first release.\n',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
|
|
|
test("a text's changes are its lines starting with '- '", () {
|
|
|
|
|
expect(whatsNewChanges('- One\n\n - Two \n'), ['One', 'Two']);
|
|
|
|
|
expect(whatsNewList('- One\n\n - Two \n'), '- One\n- Two');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
group('problems', () {
|
|
|
|
|
test('none in an edited text', () {
|
|
|
|
|
expect(whatsNewProblems('x', '- One.\n- Two.\n'), isEmpty);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a text listing nothing', () {
|
|
|
|
|
expect(whatsNewProblems('x', '\n'), ['x lists no change.']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a line not starting with a dash', () {
|
|
|
|
|
expect(whatsNewProblems('x', '- One.\nTwo.\n'), [
|
|
|
|
|
"x, line 2: doesn't start with '- '.",
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('a line still to be translated', () {
|
|
|
|
|
expect(whatsNewProblems('x', '- TRANSLATE: One.\n'), [
|
|
|
|
|
'x is still to be translated (TRANSLATE:).',
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("more than F-Droid's 500 characters", () {
|
|
|
|
|
expect(whatsNewProblems('x', '- ${'a' * 499}\n'), [
|
|
|
|
|
"x has 501 characters, more than F-Droid's 500.",
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("the files are the app's, F-Droid's changelog its listing's", () {
|
|
|
|
|
expect(
|
|
|
|
|
whatsNewFile(testApp, const Version(0, 19, 0), 'de'),
|
|
|
|
|
'apps/notes/halkyon_notes_flutter/assets/whats_new/0.19.0.de.txt',
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
fdroidChangelogFile(testApp, 23, 'en'),
|
|
|
|
|
'app.halkyon.notes/en-US/changelogs/23.txt',
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
fdroidChangelogFile(testApp, 23, 'de'),
|
|
|
|
|
'app.halkyon.notes/de/changelogs/23.txt',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// The texts every app bundles, as a release would take them.
|
|
|
|
|
for (final app in releasedApps.values) {
|
|
|
|
|
test("${app.name}'s texts of what's new are each release's in every "
|
|
|
|
|
'language, and as a release takes them', () {
|
|
|
|
|
final folder = Directory(app.whatsNewDirectory);
|
|
|
|
|
final byVersion = <String, Set<String>>{};
|
|
|
|
|
for (final file in folder.listSync().whereType<File>()) {
|
|
|
|
|
final name = p.basename(file.path);
|
|
|
|
|
final match = whatsNewName.firstMatch(name);
|
|
|
|
|
expect(match, isNotNull, reason: '$name: <version>.<language>.txt');
|
|
|
|
|
(byVersion[match![1]!] ??= {}).add(match[2]!);
|
|
|
|
|
expect(whatsNewProblems(name, file.readAsStringSync()), isEmpty);
|
|
|
|
|
}
|
|
|
|
|
expect(byVersion, isNotEmpty);
|
|
|
|
|
for (final MapEntry(key: version, value: languages)
|
|
|
|
|
in byVersion.entries) {
|
|
|
|
|
expect(languages, whatsNewLanguages.toSet(), reason: version);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
build(release): what's new drafted from the commits' Whats-New trailers, other apps' commits left out
The drafts came from the commits' subjects, which are written for
developers: each of the releases of 4 Nov 2026 (Todo 0.2.0, Train 0.3.0,
Learn 0.24.0) listed every commit since the last, Meditate's among them
where it touched a shared package, more than twice F-Droid's 500
characters, the German all still to translate, and each had to be written
again whole.
A commit people would notice now says so in its message's trailers,
`Whats-New: <sentence>` once a change and `Whats-New-De: <Satz>` in the
same order, and the draft is those lines alone, the features' first, a
missing translation marked TRANSLATE:. Only where no commit of the
release carries one does it fall back to the subjects. A commit whose
scope names apps (feat(todo,meditate): ...) counts for those alone, any
other for every app it touches. Over a running next.<language>.txt per
app, which parallel branches conflict on at every merge, and over an
assistant drafting at release time (a key, the commits sent to a
provider, a different text each run). ADR 0030, D61.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-04 20:19:12 +01:00
|
|
|
|
|
|
|
|
const _apps = {'notes', 'diary'};
|