Compare commits

..
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 4515f24268 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 05:45:53 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 5145d91597 fix: add _tea_get, merged-PR catch-up to close issues on merge (#305)
Why State/Question appeared on issue #282: the loop waited 15 min for a
CI run on branch issue-282-fix, found none (ci.yml path filter did not
trigger for workflow-file-only changes), and set State/Question.

Fixes two related issues:

1. Add `_tea_get()` — a direct Codeberg API client using urllib.  Refactor
   `_latest_main_ci_run` and `_latest_ci_run_for_branch` to use it (the
   latter now matches PR runs via `event_payload` instead of resolving the
   PR number first).  Also use it for the `mergeable` check in
   `_handle_pr_still_open_after_merge`.  This fixes 14 pre-existing test
   failures that mocked `agent_loop._tea_get` but the function didn't exist.

2. Add `_merged_issue_prs()` and a catch-up scan in `_run_loop()`: on each
   tick the loop now finds merged `issue-{N}-fix` PRs whose issues still
   carry state labels and closes them.  This handles the case where
   State/Question was set (e.g., CI path-filter miss) but the PR was later
   merged manually — the next cron tick will close the issue automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:06:21 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 de3b99a0d6 feat: add --dangerously-skip-permissions to all claude --resume output (#304)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:47:19 +02:00
3 changed files with 71 additions and 98 deletions
+70 -73
View File
@@ -120,76 +120,15 @@ class _AccountTile extends ConsumerWidget {
final health = ref.watch(syncHealthProvider(account.id));
final typeLabel = account.type == AccountType.jmap ? 'JMAP' : 'IMAP';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
leading: const Icon(Icons.account_circle),
title: Text(account.displayName),
subtitle: Text('${account.email}\n$typeLabel'),
isThreeLine: true,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
status.when(
loading: () => const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
data: (_) =>
const Icon(Icons.check_circle, color: Colors.green),
error: (e, _) => Tooltip(
message: e.toString(),
child: const Icon(Icons.error_outline, color: Colors.red),
),
),
PopupMenuButton<_AccountAction>(
onSelected: (action) => _onAction(context, action),
itemBuilder: (_) => [
const PopupMenuItem(
value: _AccountAction.syncLog,
child: Text('Sync log'),
),
const PopupMenuItem(
value: _AccountAction.verifySync,
child: Text('Verify sync health'),
),
const PopupMenuItem(
value: _AccountAction.forceSync,
child: Text('Force full sync'),
),
const PopupMenuItem(
value: _AccountAction.edit,
child: Text('Edit'),
),
if (_sieveSupported(account))
const PopupMenuItem(
value: _AccountAction.emailFiltersRemote,
child: Text('Server email filters'),
),
const PopupMenuItem(
value: _AccountAction.emailFiltersLocal,
child: Text('Local email filters'),
),
const PopupMenuItem(
value: _AccountAction.send,
child: Text('Send accounts'),
),
const PopupMenuDivider(),
const PopupMenuItem(
value: _AccountAction.delete,
child: Text('Delete'),
),
],
),
],
),
onTap: () => context.push('/accounts/${account.id}/mailboxes'),
),
Padding(
padding: const EdgeInsets.fromLTRB(72, 0, 16, 8),
child: health.when(
return ListTile(
leading: const Icon(Icons.account_circle),
title: Text(account.displayName),
subtitle: 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];
@@ -202,7 +141,7 @@ class _AccountTile extends ConsumerWidget {
color: h.isHealthy ? Colors.green : Colors.orange,
),
const SizedBox(width: 4),
Expanded(
Flexible(
child: Text(
h.isHealthy
? 'Healthy'
@@ -216,8 +155,66 @@ class _AccountTile extends ConsumerWidget {
loading: () => const Text('Sync health: checking...'),
error: (e, _) => Text('Sync health error: $e'),
),
),
],
],
),
isThreeLine: true,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
status.when(
loading: () => const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
data: (_) => const Icon(Icons.check_circle, color: Colors.green),
error: (e, _) => Tooltip(
message: e.toString(),
child: const Icon(Icons.error_outline, color: Colors.red),
),
),
PopupMenuButton<_AccountAction>(
onSelected: (action) => _onAction(context, action),
itemBuilder: (_) => [
const PopupMenuItem(
value: _AccountAction.syncLog,
child: Text('Sync log'),
),
const PopupMenuItem(
value: _AccountAction.verifySync,
child: Text('Verify sync health'),
),
const PopupMenuItem(
value: _AccountAction.forceSync,
child: Text('Force full sync'),
),
const PopupMenuItem(
value: _AccountAction.edit,
child: Text('Edit'),
),
if (_sieveSupported(account))
const PopupMenuItem(
value: _AccountAction.emailFiltersRemote,
child: Text('Server email filters'),
),
const PopupMenuItem(
value: _AccountAction.emailFiltersLocal,
child: Text('Local email filters'),
),
const PopupMenuItem(
value: _AccountAction.send,
child: Text('Send accounts'),
),
const PopupMenuDivider(),
const PopupMenuItem(
value: _AccountAction.delete,
child: Text('Delete'),
),
],
),
],
),
onTap: () => context.push('/accounts/${account.id}/mailboxes'),
);
}
+1 -1
View File
@@ -5,7 +5,7 @@
],
"labels": ["dependencies"],
"github-actions": {
"enabled": false
"fileMatch": ["^\\.forgejo/workflows/[^/]+\\.ya?ml$"]
},
"packageRules": [
{
-24
View File
@@ -252,29 +252,5 @@ 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));
},
);
});
}