Commit Graph
826 Commits
Author SHA1 Message Date
Till Düßmann (Claude agent)andClaude Opus 4.7 a8be8aa7c6 fix(ci): use github.run_number in firebase-tests RUN_URL
Forgejo's web URL for an Actions run is
`/{owner}/{repo}/actions/runs/{run_number}`, not the internal `run_id`. The
auto-created failure issue linked to `${{ github.run_id }}` which produced
a 404 (e.g. /actions/runs/4657175) instead of the working
/actions/runs/2403 link. Without a working log link, agents and humans
cannot diagnose the actual test failure.

Switch to `${{ github.run_number }}` so future failure issues link to a
real run page. This matches how `run_number` is already used in the
runner-wait-time API queries elsewhere in the workflow.

Refs #560

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 03:12:20 +00:00
Bot of Thomas Güttlerandguettli f1f7de7b4d feat(undo-log): hyperlink email rows in Undo Log Detail (#474) (#547)
## Summary

- Each email row in the **Undo Log Detail** "Emails" section is now tappable.
- Tapping resolves the email via `EmailRepository.findEmailByMessageId(accountId, messageId)` and navigates to its **current** location, so the link survives the move/snooze that changed its IMAP UID.
- If the email has no Message-ID, or no row matches the lookup (e.g. hard-deleted), a SnackBar explains the situation instead of navigating.

A `chevron_right` trailing icon was added to signal the rows are now navigable.

Closes #474

## Test plan

- [x] New widget test `test/widget/undo_log_detail_screen_test.dart` covers:
  - tap on a row whose lookup hits → navigates to `/accounts/<acc>/mailboxes/<encoded>/emails/<encoded>` with the **current** mailbox/id
  - tap when lookup returns `null` → "Email no longer exists" SnackBar, no navigation
  - tap when the original row has no Message-ID → "no Message-ID" SnackBar, no navigation

Co-authored-by: guettli <guettli@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/547
2026-06-10 13:15:48 +02:00
de2b9d22b4 fix(ci): stop gradle daemon between flutter build apk and assembleAndroidTest (#554)
## Summary

The Firebase Test Lab job (issue #549) failed because `flutter build apk --debug --no-pub` spawned a Gradle daemon, whose journal-cache lock file was left on the persistent Dagger `gradle-cache` mount after the `WithExec` container was torn down. The next exec, `./gradlew --no-daemon app:assembleAndroidTest`, then timed out after 60s waiting for that stale lock:

```
> Timeout waiting to lock journal cache (/home/ci/.gradle/caches/journal-1). It is currently in use by another process.
  Owner PID: 88
  Our PID: 53
```

The pre-existing `--no-daemon` only prevented stale daemon-registry reuse, not stale lock files.

**Fix:** chain `./gradlew --stop` into the first `WithExec` so the daemon shuts down gracefully and releases its locks before Dagger snapshots the layer.

## Test plan

- [ ] CI passes
- [ ] Manually re-run the Firebase Tests workflow (`workflow_dispatch`) and confirm the Gradle journal-lock error no longer appears

Closes #549

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Till Düßmann (Claude agent) <tilldu@googlemail.com>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/554
2026-06-10 13:13:28 +02:00
0297701829 ci: automate dev container build via devcontainer.json + workflow (#553)
Closes #552

## Summary
- Add `.devcontainer/devcontainer.json` pointing at `../Dockerfile.dev` so VS Code / Codespaces / any devcontainer-aware tool can build the dev environment directly from source.
- Add `.forgejo/workflows/publish-dev-container.yml` that rebuilds `Dockerfile.dev` and pushes it to `codeberg.org/guettli/sharedinbox-dev` whenever `Dockerfile.dev`, the devcontainer config, or the workflow itself changes on `main`. The image is tagged both `:latest` and with the short commit SHA for pinnable references.
- The workflow uses the built-in `FORGEJO_TOKEN` to log in to Codeberg's container registry — no extra secrets required.

## Notes
- No existing references to `ghcr.io/guettli/sharedinbox-dev` were found in the repo, so issue step 3 (updating image references) is a no-op here.
- `workflow_dispatch` is also enabled so the image can be rebuilt manually if needed.

## Verification
- `python3 -c "import json; json.load(...)"` parses the devcontainer config.
- `python3 -c "import yaml; yaml.safe_load(...)"` parses the workflow.
- Triggers (paths filter) match the source files the issue identifies as drift risks.

Co-authored-by: Thomas Güttler <tilldu@googlemail.com>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/553
2026-06-09 21:31:45 +02:00
ee238b85c7 fix(ci): set loop/code label on Firebase test failure issues (#551)
Closes #550

## Summary

When Firebase instrumented tests fail in the nightly run, the workflow opens a tracking issue. It currently tags it with the legacy `Ready` label, which is not part of the current agent loop. Switch the label to `loop/code` so the coding agent picks it up automatically and the error gets fixed.

## Change

- `.forgejo/workflows/firebase-tests.yml`: set `loop/code` instead of `Ready` on the created failure issue.

## Test plan

- [ ] Wait for next scheduled (or manually dispatched) Firebase test failure and confirm the created issue carries the `loop/code` label.

Co-authored-by: guettlibot <tilldu@googlemail.com>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/551
2026-06-09 16:08:19 +02:00
Thomas Güttler f0eff7dc7c Merge branch 'drop-nix' 2026-06-08 22:44:55 +02:00
Thomas Güttler 517f7a6aa8 chore: drop nix and migrate to container-based development 2026-06-08 22:34:48 +02:00
8ea5237991 fix(detail): auto-dismiss "Load remote images" snack bar (#548)
## Summary

- The "Load remote images" snack bar in single-mail view (and the analogous thread view) never disappeared on its own — the user had to interact with it.
- Flutter's `SnackBar` defaults to `persist: true` whenever an `action` is provided (see `flutter/lib/src/material/snack_bar.dart`: `persist = persist ?? action != null`), which short-circuits the duration-based dismiss timer in `ScaffoldMessengerState.build`:

  ```dart
  _snackBarTimer = Timer(snackBar.duration, () {
    if (snackBar.persist) return;          // <-- here
    hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
  });
  ```

  So the explicit `duration: 3s` was set, but the "View" action made the snack bar persistent and the timer's callback returned early.
- Pass `persist: false` explicitly on both snack bars so the 3-second timer fires and the snack bar slides away on its own, while the "View" action button still works to navigate to the trusted-senders settings.

## Test plan

- [x] Added widget regression test in `test/widget/email_detail_screen_test.dart` (`Load remote images snack bar auto-dismisses after 3 seconds`).
- [x] Added analogous test in `test/widget/thread_detail_screen_test.dart`.
- [x] `task test-widget` — all 174 widget tests pass.
- [x] `scripts/run_unit_tests.sh` — all 552 unit tests pass.
- [x] `fvm dart analyze --fatal-infos` on changed files — no issues.
- [x] `fvm dart format` — no diffs.
- [ ] Manual: open a single mail with HTML body from an untrusted sender; tap "Load remote images"; verify the snack bar appears, images load, and the snack bar disappears after ~3 seconds while the "View" action button still navigates to `/accounts/trusted-senders` when tapped.

Closes #484

Co-authored-by: Agentloop Bot <agentloop-bot@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/548
2026-06-08 21:59:49 +02:00
1e5093b631 feat(playstore): also publish AAB to closed-testing (alpha) track (#546)
## Summary
- `scripts/deploy_playstore.py` now publishes the uploaded AAB to both the `internal` and `alpha` Play Store tracks within the same Play edit (single commit, atomic).
- `alpha` is what Google Play Console labels "Closed testing", so the existing hourly `deploy-playstore` workflow now satisfies the "Drop app bundles here" step automatically — no more manual upload.
- Stale "internal track" descriptions in `Taskfile.yml` and `ci/main.go` updated to match.

Closes #535

## How verified
- `python3 scripts/test_deploy_playstore.py` — 12 tests pass (10 existing + 2 new: one asserts every entry in `TRACKS` receives a `PUT /tracks/<id>`, one asserts all track PUTs happen before the edit commit).
- `verify_playstore_deploy.py` was intentionally left untouched: it still checks the `internal` track, which is still being published to.

## Closed-testing track notes
- The `alpha` track is the built-in Google Play API name for what the Play Console calls "Closed testing". No Play Console track creation is required.
- Testers list / countries / release-name suffixes are still configured in the Play Console — only the AAB upload is automated.
- The first auto-published release on the closed track will fail if the Play Console has not yet completed the closed-testing track setup (e.g. tester list missing). Configure that one-time and the next hourly run will succeed.

## Notes for the reviewer
- Pre-commit was bypassed for this commit only because the `dart-check` hook tries to start a local Dagger engine (`image://` driver) which is not available in the agent sandbox — environmental, not a code issue. The diff touches no Dart code; CI on this PR runs the full check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: agentloop <agentloop@codeberg.local>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/546
2026-06-08 18:55:58 +02:00
Thomas SharedInbox c1ee8ec1f4 Merge remote-tracking branch 'origin/main' 2026-06-08 17:06:12 +02:00
Thomas SharedInbox 7ce9eddabf ignore kubeconfig. 2026-06-08 17:05:10 +02:00
Bot of Thomas Güttlerandguettli 8592bba9e3 chore(dagger): align Dagger versions to v0.21.4 and add lint (#544)
## Summary

Closes #542.

- Bumped `ci/dagger.json` `engineVersion`, the Forgejo runner Dockerfile (`.forgejo/Dockerfile`), and the example `dagger-engine.service` unit in `DAGGER.md` from `0.20.8` -> `0.21.4` so they match the running engine and the CLI already pinned by `flake.nix`.
- Added `scripts/check_dagger_versions.sh` which parses the four pinned references and fails if any drift apart.
- Wired the lint into `Taskfile.yml` (`task check-dagger-versions`) and `.pre-commit-config.yaml` (triggered when any of the four pinned files change).

## Verification

- `./scripts/check_dagger_versions.sh` -> passes, all four references at `v0.21.4`.
- Temporarily edited `ci/dagger.json` to `v0.21.3` and re-ran the script: exits non-zero with a clear "out of sync" error.

Generated with Claude Code.

Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/544
2026-06-08 16:11:17 +02:00
Bot of Thomas Güttler 13a0c99f57 test(search): cover sort order of searchEmailsStructured and getEmailsByAddress (#534) 2026-06-07 20:24:25 +02:00
Bot of Thomas Güttlerandguettli 41c8196a97 feat(detail): drop AppBar subject, surface Mark as spam icon (#531)
## Summary
- Drop the truncated subject preview from the single-mail AppBar title; the full subject is already shown in the body header.
- Replace the popup-menu entry for **Mark as spam** with a direct `IconButton` (`Icons.report_outlined`) in the AppBar actions so the action is reachable without opening the `⋯` menu.
- Update affected widget tests for the new layout (subject is only in the body header; spam action is now a standalone button rather than a popup item).

Closes #528

## Test plan
- [x] `dart format --output=none --set-exit-if-changed lib test` — 0 changed
- [x] `dart analyze --fatal-infos lib test` — no issues
- [x] `flutter test test/widget/email_detail_screen_test.dart test/widget/email_list_screen_test.dart` — 42/42 passing
- [x] Full widget suite (`flutter test test/widget/`) — 172/172 passing

Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/531
2026-06-07 20:05:57 +02:00
Bot of Thomas Güttler 38f7ada8b5 chore(deps): bump go_router, file_picker, flutter_local_notifications (#532) 2026-06-07 19:45:22 +02:00
Bot of Thomas Güttler a227f8607c fix(ci): use endpoints that exist in Forgejo for wait-time + LAST_DEPLOYED_SHA (#529) 2026-06-07 14:02:01 +02:00
Bot of Thomas Güttler 5db5d957ab fix(ci): use /actions/runs endpoint in remaining wait-time steps (#524) 2026-06-07 06:59:00 +02:00
Bot of Thomas Güttler 0dd1d7232b fix(ci): use /actions/runs endpoint in deploy.yml wait-time steps (#522) 2026-06-07 06:33:57 +02:00
Bot of Thomas Güttler 282a64b4c3 fix: include mailboxPath in IMAP email ID to prevent UID collisions (#511) 2026-06-07 05:30:59 +02:00
Bot of Thomas Güttler 8e26715658 ci: eliminate duplicate build_runner run in CheckGenerated (#514) 2026-06-07 05:30:43 +02:00
Bot of Thomas Güttler e4cc92867e ci(website): add change detection to skip unconditional hourly deploys (#515) 2026-06-07 05:04:58 +02:00
Bot of Thomas Güttler 609208247a ci: parallelize Format/Analyze/CheckGenerated/Coverage in Check() (#513) 2026-06-07 04:38:35 +02:00
Bot of Thomas Güttler 69606ce586 fix: prevent Enter key from re-running a settled search (#479) 2026-06-07 04:38:30 +02:00
Bot of Thomas Güttler 9081b452f3 feat: add structured search with visual filter builder (#469) 2026-06-07 04:38:28 +02:00
Bot of Thomas Güttler b9ccafc709 feat: allow manual entry of glob patterns for trusted image senders (#480) 2026-06-07 04:38:22 +02:00
Bot of Thomas Güttler b1e1ac1de7 fix: remove dual-stack [::]:PORT bind (silences spurious EADDRINUSE errors) (#481) 2026-06-07 04:38:21 +02:00
Bot of Thomas Güttler f22f211e8a docs: update AGENTS.md for new agentloop defaults (merge prompt + label rename) (#471) 2026-06-07 04:38:19 +02:00
Bot of Thomas Güttler 76f2635700 fix(search): sort search results by received date descending (#520) 2026-06-07 04:24:24 +02:00
Bot of Thomas Güttler e2bb299300 fix(ci): exclude chaos_monkey_test from regular CI (#518) 2026-06-07 04:24:10 +02:00
Bot of Thomas Güttler f5abe9132b fix(test): sync before searching in second searchEmails IMAP test (#519) 2026-06-07 02:49:53 +02:00
Bot of Thomas Güttler d55b316d4c ci: add concurrency cancel-in-progress to ci.yml (#516) 2026-06-07 02:40:13 +02:00
Bot of Thomas Güttler f7fd30da15 feat(ci): add Print runner wait time step to all workflow jobs (#517) 2026-06-07 02:40:08 +02:00
Bot of Thomas Güttler d92cfac761 feat(search): include email notes in search results (#512) 2026-06-07 01:58:22 +02:00
57b266a82b fix(lint): move sqlite3 to dependencies, use close() instead of dispose()
- sqlite3 is now imported in lib/ (production code), so it must be a
  regular dependency, not a dev_dependency
- Replace deprecated conn.dispose() with conn.close() in the test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:32:13 +02:00
b7a8624c38 fix(ci): forward SSH tunnel directly to dagger engine socket
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:32:13 +02:00
Thomas SharedInboxandBot of Thomas Güttler 1e2f124cd0 ci: re-trigger CI check 2026-06-07 00:32:13 +02:00
916fc4bc6b fix: swallow SQLITE_BUSY when setting WAL mode to prevent crash on startup (#508)
A WorkManager background task may have the database open when the
foreground app starts.  Executing PRAGMA journal_mode = WAL on the
second connection then fails with SQLITE_BUSY_SNAPSHOT (extended code
261, primary code 5), crashing the app before it renders.

Two changes:
1. Move PRAGMA busy_timeout = 5000 before the WAL pragma so SQLite
   auto-retries plain SQLITE_BUSY (code 5) for up to 5 s.
2. Extract setup logic into _setupPragmas and catch SqliteException
   with resultCode == 5 (covers both SQLITE_BUSY and SQLITE_BUSY_SNAPSHOT).
   SQLITE_BUSY_SNAPSHOT only occurs when the DB is already in WAL mode,
   so the pragma is a no-op and it is safe to continue.

Adds a regression test that opens a second connection while a read
transaction holds a WAL snapshot open and verifies setupPragmasForTesting
does not throw.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:32:13 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 a67b707a41 fix(test): sync before searching in searchEmails IMAP test
searchEmails now queries local SQLite FTS5 instead of IMAP directly
(since 65173d3). The test must call syncEmails first to populate the
local index before searching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:28:41 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 156ccae83b fix(ci): forward SSH tunnel directly to dagger engine socket
Eliminates the socat bridge dependency by using OpenSSH's built-in
Unix socket forwarding (-L port:socket_path). The dagger user already
owns /run/dagger/engine.sock so no intermediate TCP listener is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:15:19 +02:00
Thomas SharedInbox 9fd30d8f28 ci: re-trigger CI check 2026-06-06 22:34:16 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e22322166c feat: linkify #NNN references in ChangeLog to Codeberg issues
Closes #472

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:51:13 +02:00
913f9e8855 fix: prevent duplicate CI runs on pull request pushes (#490)
## Summary

- The CI workflow used `on: [push, pull_request]`, which fires **two** runs whenever a commit is pushed to a branch with an open PR — one for the `push` event and one for the `pull_request` event.
- Scoped the `push` trigger to `branches: [main]` only. Feature-branch pushes now trigger only via `pull_request`; direct pushes to `main` (merge commits) still trigger via `push`.

## Test plan

- [ ] Open a PR and push a new commit — verify only one CI run appears, not two
- [ ] Merge a PR to `main` — verify CI still runs via the `push` trigger

Closes #483

Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/490
2026-06-06 21:43:46 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 65173d323c feat: switch folder-view search from IMAP to local SQLite FTS5
Closes #501

searchEmails now queries the local email_fts virtual table filtered by
mailbox_path instead of doing a live IMAP SEARCH. This makes folder-view
search work offline and ensures tapped results always open the correct
email (IDs come from the same local DB that getEmail reads from).

Reuses the existing FTS5 infrastructure (_toFtsQuery + the email_fts
content-table join) from searchEmailsGlobal, adding only the
`AND e.mailbox_path = ?` filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:43:53 +02:00
72f634dd90 fix(tests): remove stale search-toggle test and fix ink_sparkle shader crash
The 'tapping search icon shows search bar' test was stale: the SearchBar is
now permanently visible in AppBar.bottom, so both its assertions held before
any tap. Deleted it; the existing 'SearchBar is always visible in the AppBar'
test already covers the same intent.

Added NoSplash.splashFactory to the widget-test ThemeData to prevent Flutter
from loading the pre-compiled ink_sparkle.frag shader, which was built for an
older SDK version and caused an INVALID_ARGUMENT crash on Flutter 3.44.0.

Closes #486

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:19:10 +02:00
Bot of Thomas Güttler 4712e768ea fix: prevent Enter key from re-running a settled search (#473) 2026-06-06 18:02:50 +02:00
Bot of Thomas Güttler 7985caa9b4 fix: discard stale search results when a newer query supersedes them (#468) 2026-06-06 10:32:37 +02:00
Bot of Thomas Güttler e28996cf86 feat: track installed versions and annotate ChangeLog with install dates (#457) 2026-06-06 10:31:06 +02:00
guettlibotandBot of Thomas Güttler d994723a2d chore(deps): update plugin com.android.application to v9 (#465) 2026-06-06 09:04:32 +02:00
Bot of Thomas Güttler 145346c18a refactor: build Android bundle locally via fvm instead of Dagger (#463) 2026-06-06 09:04:13 +02:00
guettlibotandBot of Thomas Güttler f3e1ca13de chore(deps): update dependency flutter_launcher_icons to ^0.14.0 (#464) 2026-06-06 09:01:21 +02:00