## 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
## 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
- 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>
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>
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>
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>
## 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
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>
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>
## Summary
- Tapping a row in the Undo Log list opens a new `UndoLogDetailScreen`
- Detail screen shows: account ID, action type (with icon/colour), timestamp, source folder, destination folder (move only), and a list of all emails in the transaction (subject + sender)
- Navigation uses go_router nested route `/accounts/undo-log/:actionId` with `state.extra` to pass the `UndoAction` object
- AppBar has an **Undo** button that calls the existing undo service and pops back
## Also fixed
- `flake.nix`: replaced the broken dagger/nix 0.20.8 Nix wrapper (infinite self-exec loop) with a direct 0.21.4 `fetchurl` derivation; wired `DAGGER_HOST` so the pre-commit `dart-check` hook can reach the running engine
- `pubspec.lock`: bumped `meta` 1.17→1.18 and `test` 1.30→1.31 to match what the CI resolver picks up (eliminates spurious generated-files drift in CI)
## Verification
- `task test` — all 492 unit/widget tests pass
- `dart analyze --fatal-infos` — clean (no warnings or infos)
- Pre-commit hooks (including `dart-check` via Dagger) — all passed on commit
Closes#450
Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/461
## Summary
- The Forgejo/GitHub Actions runner only redacts values it has been explicitly told about. Secrets exported via `$GITHUB_ENV` in `setup_dagger_remote.sh` were never registered, so they could appear in plain text in CI log output.
- Added `::add-mask::` calls for every secret exported by `export_secret()`, and for the two inline variables `DAGGER_SSH_KEY` and `DAGGER_ENGINE_HOST` that bypass that function.
- Multiline values (e.g. SSH private keys, JSON key files) are masked line-by-line, since `::add-mask::` covers a single line at a time.
## Test plan
- [ ] Trigger a `workflow_dispatch` run of `deploy.yml` and confirm no secret values appear in plain text in the "Setup Dagger Remote Engine" step or any subsequent steps.
- [ ] Confirm the existing `[secrets] exported NAME (N chars)` log lines still appear (they log only the name and length, not the value).
Closes#434
Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/460
Closes#451
## What changed
Replaces the default Flutter blue logo with the project's rainbow-rings `icon.svg` on all supported platforms.
**Android** — all five mipmap densities regenerated (`mdpi` 48px through `xxxhdpi` 192px).
**Linux** — `linux/sharedinbox.png` (512×512) added, installed next to the binary via `CMakeLists.txt`, and set as the GTK window icon via `gtk_window_set_icon_from_file` in `my_application.cc`.
**Tooling** — `icon.png` (1024×1024 source raster) committed; `flutter_launcher_icons` added as dev dep with a `flutter_icons` config block; `task generate-icons` added to `Taskfile.yml` for future regeneration; `librsvg` added to `flake.nix` so `rsvg-convert` is available inside `nix develop`.
## How verified
Icons were generated with Inkscape from `icon.svg` and visually confirmed (rainbow-rings design appears correctly at all sizes). The `playstore/icon.png` was already correct and unchanged.
Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/459
## Summary
- Deletes `scripts/build_android_bundle_local.sh`, which required a host Android SDK and failed with `No Android SDK found`
- Removes the `build-android-bundle-local` Taskfile task that invoked it
- Rewrites `deploy-android-bundle` to call the existing Dagger `publish-android` pipeline (build → stamp versionCode → sign → upload) via `sops exec-env` for local secret injection — no local Android SDK needed
The `publish-android` Dagger function (`ci/main.go`) already handles everything the old script did (keystore decode, AAB build, signing) plus version-code stamping, so no changes to `ci/main.go` are required.
Closes#444🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/449
## Summary
- Adds a custom Renovate manager that reads the pinned Flutter version from `.fvmrc`
- Uses `ghcr.io/cirruslabs/flutter` as the Docker datasource so Renovate only proposes a bump when the corresponding image tag exists in the registry
- The CI pipeline (`ci/main.go`) already derives the Docker image tag from `.fvmrc` at runtime — `.fvmrc` is the single source of truth; no other files need grouping
## How it works
Renovate checks `ghcr.io/cirruslabs/flutter` for available tags. If `3.44.1` doesn't exist yet, no PR is opened. Once the image is published, Renovate opens a PR to bump `.fvmrc` — the only file that needs to change.
## Verification
- `renovate.json` schema validated
- Reviewed `ci/main.go`: `FlutterVersion` is read exclusively from `.fvmrc`; no hardcoded version strings elsewhere require additional grouping rules
Closes#447
Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de>
Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/452