Compare commits

...
Author SHA1 Message Date
Thomas SharedInbox 551ac67daa fix: move sync health report to its own row below each account tile (#311)
The sync health widget was inside ListTile.subtitle, which caused it to
be squeezed to near-zero width by the trailing Row (status icon + menu).
Move it to a full-width Padding row below the ListTile so it can display
properly without layout pressure from the trailing widget.
2026-05-28 06:49:12 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 91c86d72ef fix: check issue state not labels in merged-PR catch-up
Closed issues can still have labels; the old `if not labels` guard
never fired for issue #272, so the loop retried the no-op close every
minute and never reached the ToPlan/Ready issue logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:49:12 +02:00
3 changed files with 102 additions and 73 deletions
+39 -36
View File
@@ -120,43 +120,13 @@ class _AccountTile extends ConsumerWidget {
final health = ref.watch(syncHealthProvider(account.id));
final typeLabel = account.type == AccountType.jmap ? 'JMAP' : 'IMAP';
return ListTile(
leading: const Icon(Icons.account_circle),
title: Text(account.displayName),
subtitle: Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${account.email}\n$typeLabel'),
const SizedBox(height: 4),
health.when(
data: (h) {
if (h == null) return const Text('Sync health: Not verified yet');
final date = h.lastVerifiedAt.toLocal().toString().split('.')[0];
return Row(
children: [
const Text('Sync health: '),
Icon(
h.isHealthy ? Icons.verified : Icons.warning_amber,
size: 14,
color: h.isHealthy ? Colors.green : Colors.orange,
),
const SizedBox(width: 4),
Flexible(
child: Text(
h.isHealthy
? 'Healthy'
: _formatDiscrepancies(h.discrepancySummary),
),
),
Text(' ($date)', style: const TextStyle(fontSize: 10)),
],
);
},
loading: () => const Text('Sync health: checking...'),
error: (e, _) => Text('Sync health error: $e'),
),
],
),
ListTile(
leading: const Icon(Icons.account_circle),
title: Text(account.displayName),
subtitle: Text('${account.email}\n$typeLabel'),
isThreeLine: true,
trailing: Row(
mainAxisSize: MainAxisSize.min,
@@ -167,7 +137,8 @@ class _AccountTile extends ConsumerWidget {
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
data: (_) => const Icon(Icons.check_circle, color: Colors.green),
data: (_) =>
const Icon(Icons.check_circle, color: Colors.green),
error: (e, _) => Tooltip(
message: e.toString(),
child: const Icon(Icons.error_outline, color: Colors.red),
@@ -215,6 +186,38 @@ class _AccountTile extends ConsumerWidget {
],
),
onTap: () => context.push('/accounts/${account.id}/mailboxes'),
),
Padding(
padding: const EdgeInsets.fromLTRB(72, 0, 16, 8),
child: health.when(
data: (h) {
if (h == null) return const Text('Sync health: Not verified yet');
final date = h.lastVerifiedAt.toLocal().toString().split('.')[0];
return Row(
children: [
const Text('Sync health: '),
Icon(
h.isHealthy ? Icons.verified : Icons.warning_amber,
size: 14,
color: h.isHealthy ? Colors.green : Colors.orange,
),
const SizedBox(width: 4),
Expanded(
child: Text(
h.isHealthy
? 'Healthy'
: _formatDiscrepancies(h.discrepancySummary),
),
),
Text(' ($date)', style: const TextStyle(fontSize: 10)),
],
);
},
loading: () => const Text('Sync health: checking...'),
error: (e, _) => Text('Sync health error: $e'),
),
),
],
);
}
+5 -3
View File
@@ -912,9 +912,11 @@ def _run_loop() -> int:
if not m:
continue
issue_num = int(m.group(1))
labels = _get_issue_labels(issue_num)
if not labels:
# Issue is likely already closed — skip.
try:
issue_data = _tea_get(f"/repos/{REPO}/issues/{issue_num}")
except RuntimeError:
continue
if issue_data.get("state") != "open":
continue
pr_number = pr["number"]
print(f"Catch-up (merged PR): PR #{pr_number} for issue #{issue_num} was merged — closing.")
+24
View File
@@ -252,5 +252,29 @@ void main() {
expect(find.textContaining('flag mismatches: 1'), findsOneWidget);
},
);
testWidgets(
'sync health row is positioned below the account name row',
(tester) async {
await tester.pumpWidget(
buildApp(
initialLocation: '/accounts',
overrides: baseOverrides(
accounts: [kTestAccount],
syncHealth: SyncHealthRow(
accountId: kTestAccount.id,
lastVerifiedAt: DateTime(2024, 6),
isHealthy: true,
),
),
),
);
await tester.pumpAndSettle();
final namePos = tester.getTopLeft(find.text('Alice')).dy;
final healthPos = tester.getTopLeft(find.textContaining('Healthy')).dy;
expect(healthPos, greaterThan(namePos));
},
);
});
}