In ChangeLog show when the user updated to which version #446

Closed
opened 2026-06-05 16:40:40 +00:00 by guettli · 2 comments
guettli commented 2026-06-05 16:40:40 +00:00 (Migrated from codeberg.org)

In ChangeLog show when the user updated to which version.

Store the current app version in a table. Add a new row, when the version is not in the column yet (at app start). Add current date and time.

In the ChangeLog view break current list each time a git hash is in the installed-versions table.

The user should see date, time. Without seconds. Hour and Minute is enough.

In ChangeLog show when the user updated to which version. Store the current app version in a table. Add a new row, when the version is not in the column yet (at app start). Add current date and time. In the ChangeLog view break current list each time a git hash is in the installed-versions table. The user should see date, time. Without seconds. Hour and Minute is enough.
guettlibot commented 2026-06-05 16:50:04 +00:00 (Migrated from codeberg.org)

Now I have everything I need. Let me write the plan.


Implementation Plan: Track & Display Installed Versions in ChangeLog

Overview

Add an InstalledVersions DB table, record the current GIT_HASH at each first launch of a new version, and annotate the ChangeLog markdown with installation timestamps wherever a heading matches a known installed version.


Step 1 — Add InstalledVersions Drift table (schema v39)

File: lib/data/db/database.dart

Add a new table class before the // ── Database ── section:

@DataClassName('InstalledVersionRow')
class InstalledVersions extends Table {
  TextColumn get gitHash => text()();
  DateTimeColumn get installedAt => dateTime()();

  @override
  Set<Column> get primaryKey => {gitHash};
}

Add InstalledVersions to the @DriftDatabase(tables: [...]) list.

Add the migration block:

if (from < 39) {
  await m.createTable(installedVersions);
}

File: lib/core/db_schema_version.dart — bump the constant to 39.

After editing, run:

dart run build_runner build --delete-conflicting-outputs

Step 2 — Add a DB method to record & query versions

File: lib/data/db/database.dart, inside AppDatabase:

/// Inserts the version only on first encounter; preserves original timestamp.
Future<void> recordInstalledVersionIfNew(String gitHash) =>
    into(installedVersions).insertOnConflictUpdate(
      InstalledVersionsCompanion(
        gitHash: Value(gitHash),
        installedAt: Value(DateTime.now()),
      ),
    );
    // Use insertOrIgnore (conflict action = nothing) so the timestamp
    // is NOT overwritten on subsequent launches of the same build.

Use mode: InsertMode.insertOrIgnore (Drift's DoNothing) to preserve the original installedAt.

Also add a query helper:

Future<Map<String, DateTime>> loadInstalledVersions() async {
  final rows = await select(installedVersions).get();
  return {for (final r in rows) r.gitHash: r.installedAt};
}

Step 3 — Record the current version at app start

File: lib/main.dart, in _SharedInboxAppState.initState(), after the existing ref.read(...) calls:

const _gitHash = String.fromEnvironment('GIT_HASH');
// in initState:
if (_gitHash.isNotEmpty) {
  unawaited(
    ref.read(appDatabaseProvider).recordInstalledVersionIfNew(_gitHash),
  );
}

Use the existing Riverpod DB provider (from di.dart) — the same one used by sync and other services. The if (gitHash.isEmpty) guard ensures dev builds without an injected hash are silently skipped.


Step 4 — Update ChangeLogScreen to show installation markers

File: lib/ui/screens/changelog_screen.dart

Convert from StatelessWidget to ConsumerWidget. Instead of rendering the raw markdown string, post-process it before passing to Markdown:

Future<String> _injectInstallMarkers(String raw, Map<String, DateTime> versions) async {
  if (versions.isEmpty) return raw;
  final lines = raw.split('\n');
  final buffer = StringBuffer();
  final hashPattern = RegExp(r'^#+\s+([0-9a-f]{7,40})\b');
  for (final line in lines) {
    final match = hashPattern.firstMatch(line);
    if (match != null) {
      final hash = match.group(1)!;
      final installedAt = versions[hash];
      if (installedAt != null) {
        final hhmm = '${installedAt.hour.toString().padLeft(2,'0')}:${installedAt.minute.toString().padLeft(2,'0')}';
        final date = '${installedAt.day} ${_monthName(installedAt.month)} ${installedAt.year}';
        buffer.writeln('\n---\n');
        buffer.writeln('_Installed: $hhmm, $date_\n');
      }
    }
    buffer.writeln(line);
  }
  return buffer.toString();
}

The screen loads both the asset string and db.loadInstalledVersions() concurrently with Future.wait, then passes the result to _injectInstallMarkers before rendering. Use a single FutureBuilder over a combined Future<(String, Map<String, DateTime>)>.

Month abbreviation helper (_monthName) is a simple 12-element list — no package needed.


Step 5 — Update the existing widget test

File: test/widget/changelog_screen_test.dart

The screen now requires a DB provider. Wrap the test widget in a ProviderScope that overrides appDatabaseProvider with an in-memory Drift DB (or a mock returning an empty Map). Add a test case that exercises the marker-injection path: a fake changelog with a heading matching a known hash, and a Map with that hash → a fixed DateTime, asserting the formatted time string appears in the rendered output.


Changelog format assumption

The plan assumes assets/changelog.txt uses headings whose first token is the short git hash, e.g.:

## 9ca7089 fix: enforce non-root execution
...
## adef2e9 feat: unify thread list views
...

If the actual format differs, only the hashPattern regex in Step 4 needs adjusting. The regex ^#+\s+([0-9a-f]{7,40})\b is intentionally flexible (7–40 hex chars, any heading level).

Now I have everything I need. Let me write the plan. --- ## Implementation Plan: Track & Display Installed Versions in ChangeLog ### Overview Add an `InstalledVersions` DB table, record the current `GIT_HASH` at each first launch of a new version, and annotate the ChangeLog markdown with installation timestamps wherever a heading matches a known installed version. --- ### Step 1 — Add `InstalledVersions` Drift table (schema v39) **File:** `lib/data/db/database.dart` Add a new table class before the `// ── Database ──` section: ```dart @DataClassName('InstalledVersionRow') class InstalledVersions extends Table { TextColumn get gitHash => text()(); DateTimeColumn get installedAt => dateTime()(); @override Set<Column> get primaryKey => {gitHash}; } ``` Add `InstalledVersions` to the `@DriftDatabase(tables: [...])` list. Add the migration block: ```dart if (from < 39) { await m.createTable(installedVersions); } ``` **File:** `lib/core/db_schema_version.dart` — bump the constant to `39`. After editing, run: ``` dart run build_runner build --delete-conflicting-outputs ``` --- ### Step 2 — Add a DB method to record & query versions **File:** `lib/data/db/database.dart`, inside `AppDatabase`: ```dart /// Inserts the version only on first encounter; preserves original timestamp. Future<void> recordInstalledVersionIfNew(String gitHash) => into(installedVersions).insertOnConflictUpdate( InstalledVersionsCompanion( gitHash: Value(gitHash), installedAt: Value(DateTime.now()), ), ); // Use insertOrIgnore (conflict action = nothing) so the timestamp // is NOT overwritten on subsequent launches of the same build. ``` > Use `mode: InsertMode.insertOrIgnore` (Drift's `DoNothing`) to preserve the original `installedAt`. Also add a query helper: ```dart Future<Map<String, DateTime>> loadInstalledVersions() async { final rows = await select(installedVersions).get(); return {for (final r in rows) r.gitHash: r.installedAt}; } ``` --- ### Step 3 — Record the current version at app start **File:** `lib/main.dart`, in `_SharedInboxAppState.initState()`, after the existing `ref.read(...)` calls: ```dart const _gitHash = String.fromEnvironment('GIT_HASH'); // in initState: if (_gitHash.isNotEmpty) { unawaited( ref.read(appDatabaseProvider).recordInstalledVersionIfNew(_gitHash), ); } ``` Use the existing Riverpod DB provider (from `di.dart`) — the same one used by sync and other services. The `if (gitHash.isEmpty)` guard ensures dev builds without an injected hash are silently skipped. --- ### Step 4 — Update `ChangeLogScreen` to show installation markers **File:** `lib/ui/screens/changelog_screen.dart` Convert from `StatelessWidget` to `ConsumerWidget`. Instead of rendering the raw markdown string, post-process it before passing to `Markdown`: ```dart Future<String> _injectInstallMarkers(String raw, Map<String, DateTime> versions) async { if (versions.isEmpty) return raw; final lines = raw.split('\n'); final buffer = StringBuffer(); final hashPattern = RegExp(r'^#+\s+([0-9a-f]{7,40})\b'); for (final line in lines) { final match = hashPattern.firstMatch(line); if (match != null) { final hash = match.group(1)!; final installedAt = versions[hash]; if (installedAt != null) { final hhmm = '${installedAt.hour.toString().padLeft(2,'0')}:${installedAt.minute.toString().padLeft(2,'0')}'; final date = '${installedAt.day} ${_monthName(installedAt.month)} ${installedAt.year}'; buffer.writeln('\n---\n'); buffer.writeln('_Installed: $hhmm, $date_\n'); } } buffer.writeln(line); } return buffer.toString(); } ``` The screen loads both the asset string and `db.loadInstalledVersions()` concurrently with `Future.wait`, then passes the result to `_injectInstallMarkers` before rendering. Use a single `FutureBuilder` over a combined `Future<(String, Map<String, DateTime>)>`. Month abbreviation helper (`_monthName`) is a simple 12-element list — no package needed. --- ### Step 5 — Update the existing widget test **File:** `test/widget/changelog_screen_test.dart` The screen now requires a DB provider. Wrap the test widget in a `ProviderScope` that overrides `appDatabaseProvider` with an in-memory Drift DB (or a mock returning an empty `Map`). Add a test case that exercises the marker-injection path: a fake changelog with a heading matching a known hash, and a `Map` with that hash → a fixed `DateTime`, asserting the formatted time string appears in the rendered output. --- ### Changelog format assumption The plan assumes `assets/changelog.txt` uses headings whose first token is the short git hash, e.g.: ``` ## 9ca7089 fix: enforce non-root execution ... ## adef2e9 feat: unify thread list views ... ``` If the actual format differs, only the `hashPattern` regex in Step 4 needs adjusting. The regex `^#+\s+([0-9a-f]{7,40})\b` is intentionally flexible (7–40 hex chars, any heading level).
guettlibot commented 2026-06-05 19:45:04 +00:00 (Migrated from codeberg.org)

agentloop: agent exceeded its 30m0s runtime budget and was stopped.

agent stderr tail:

[acpx] session agentloop-sharedinbox-issue-446 (fba43523-a02c-4092-bb53-954fe0c8ef26) · /home/si/agentloop/loop-data/sharedinbox/issues/446 · agent needs reconnect
agentloop: agent exceeded its 30m0s runtime budget and was stopped. agent stderr tail: ``` [acpx] session agentloop-sharedinbox-issue-446 (fba43523-a02c-4092-bb53-954fe0c8ef26) · /home/si/agentloop/loop-data/sharedinbox/issues/446 · agent needs reconnect ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: guettli/sharedinbox#446