Compare commits

..
Author SHA1 Message Date
agentloop ea860521b3 plan: refresh plan for issue #474 2026-06-08 04:56:20 +00:00
4 changed files with 46 additions and 43 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ repos:
- id: dart-check
name: dart format (autofix) + check-fast (parallel)
language: system
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && scripts/precommit_dart_check.sh'
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command dagger call --progress=plain -q -m ci --source=. check-fast'
pass_filenames: false
always_run: true
- id: ci-no-direct-dagger
+42
View File
@@ -0,0 +1,42 @@
## Goal
Make each email row in **Undo Log Detail** (`lib/ui/screens/undo_log_detail_screen.dart`) tappable, navigating to that email's current location in the app. The issue gates this on "when structured search is implemented" — structured search now exists (`lib/core/filter/filter_expression.dart`, `lib/ui/screens/search_screen.dart`), and the repository already exposes `findEmailByMessageId(accountId, messageId)` (`lib/data/repositories/email_repository_impl.dart:1899`), so we have the building block needed to locate an email regardless of moves.
## Why direct lookup, not a search-screen handoff
After a Move, the email lives at `destinationMailboxPath` with a *new* UID, so the `Email.id` stored in `UndoAction.originalEmails` is stale. The stable identifier across moves is `messageId`. `findEmailByMessageId` returns the current row (with current `mailboxPath` and `id`), giving us a one-tap deep link to the email detail screen — better UX than dumping the user into search.
## Implementation
### 1. `lib/ui/screens/undo_log_detail_screen.dart`
- Convert `_EmailTile` from `StatelessWidget` to `ConsumerWidget` so it can read `emailRepositoryProvider`.
- Take `accountId` as an additional ctor arg (passed in from the `originalEmails.map` call site so we don't depend on the email's own field, matching how the action scopes lookups).
- Add an `onTap` handler:
1. If `email.messageId == null` → no-op tap, show `SnackBar('Cannot locate this email — no Message-ID.')`.
2. Otherwise call `ref.read(emailRepositoryProvider).findEmailByMessageId(action.accountId, email.messageId!)`.
3. On hit, `context.go('/accounts/${accountId}/mailboxes/${Uri.encodeComponent(found.mailboxPath)}/emails/${Uri.encodeComponent(found.id)}')` — matches the encoding pattern used in `combined_inbox_screen.dart:280` and `email_list_screen.dart:540`.
4. On miss, show `SnackBar('Email no longer exists at its previous location. Use Undo to restore it.')` — covers the hard-deleted case and the not-yet-resynced case.
- Add `trailing: const Icon(Icons.chevron_right)` to give the row a "navigates" affordance consistent with other tappable `ListTile`s in the app.
- Leave styling otherwise unchanged; the existing `Icons.email_outlined` leading + subject/sender layout stays.
### 2. No router changes
The existing email-detail route (`router.dart:153`) is reused as-is.
### 3. No model / repository changes
`findEmailByMessageId` is already on `EmailRepository` and scoped per account, which is what we want.
### 4. Tests — `test/widget/`
Add a new widget test `test/widget/undo_log_detail_screen_test.dart` covering:
- Tapping a row whose `messageId` resolves via a fake `EmailRepository` navigates to `/accounts/<acc>/mailboxes/<encoded-path>/emails/<encoded-id>` (assert via a `GoRouter` test harness similar to `test/widget/email_detail_screen_test.dart`).
- Tapping a row when `findEmailByMessageId` returns `null` shows the "no longer exists" SnackBar and does not navigate.
- Tapping a row with `messageId == null` shows the "no Message-ID" SnackBar.
## Out of scope
- Adding Message-ID as a structured `FilterField` — not needed for direct navigation; can be revisited if a UI for "search for this email" is ever wanted.
- Changing the Undo Log list screen (`undo_log_screen.dart`) — the issue is specifically about the *detail* screen.
- Persisting/refreshing a stale `originalEmails` list — Move/Snooze update the row in place, so subsequent re-lookups by Message-ID will find them; nothing to maintain.
+3
View File
@@ -124,6 +124,9 @@
# nix develop --command does not set IN_NIX_SHELL; set it so _preflight passes in CI
export IN_NIX_SHELL=1
# Point Dagger client at the running engine socket
export DAGGER_HOST=unix:///run/dagger/engine.sock
# Disable Flutter telemetry inside dev shell
export FLUTTER_SUPPRESS_ANALYTICS=true
-42
View File
@@ -1,42 +0,0 @@
#!/usr/bin/env bash
# Pre-commit wrapper for the `dart-check` hook.
#
# `dagger call ... check-fast` needs a Dagger engine. On a dev machine or in
# CI that engine is provisioned from a local container runtime (docker/podman)
# or reached through _EXPERIMENTAL_DAGGER_RUNNER_HOST. In engine-less sandboxes
# (e.g. the agentloop agent pods that commit on our behalf) none of those
# exist, so dagger falls back to its default engine image reference and aborts
# with:
# start engine: driver for scheme "image" was not available
# which blocked every commit the agent tried to make.
#
# Codeberg CI still runs check-fast on every push, so skipping here is safe:
# warn loudly and let the commit through when no engine can be reached.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# True when dagger has some way to reach/provision an engine.
engine_available() {
# A shared engine reached over the wire wins outright.
[ -n "${_EXPERIMENTAL_DAGGER_RUNNER_HOST:-}" ] && return 0
# Otherwise dagger provisions the engine from a local container runtime.
# `info` (not `version`) confirms the daemon is actually reachable; cap it
# with a timeout so a stale docker context cannot hang the commit.
if command -v docker >/dev/null 2>&1 && timeout 10 docker info >/dev/null 2>&1; then
return 0
fi
if command -v podman >/dev/null 2>&1 && timeout 10 podman info >/dev/null 2>&1; then
return 0
fi
return 1
}
if ! engine_available; then
echo "WARNING: no Dagger engine available (no container runtime, and" \
"_EXPERIMENTAL_DAGGER_RUNNER_HOST is unset); skipping dart-check." \
"Codeberg CI still runs check-fast on push." >&2
exit 0
fi
exec nix develop --command dagger call --progress=plain -q -m ci --source=. check-fast