research: fix Draft 'Invalid Date' + stale-error leak in pipeline card
ci / gates (push) Successful in 15s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 3m58s
ci / e2e (push) Skipped
ci / publish (push) Successful in 3m46s

Two cosmetic bugs surfaced by the successful v0.8.3 pipeline run:

1. **'produced Invalid Date'** — research_outcomes.created_at was
   an OffsetDateTime serialized by time's default array format
   (`[y, ordinal, hh, mm, ss, ns, tz]`), which browser's
   `new Date(...)` can't parse. Add `#[serde(with =
   "time::serde::rfc3339")]` matching the pattern already in
   threads.rs / routine_runs.rs.

2. **Stale error text on pipeline card** — the runs stage's
   `latest_error` walked every run by `created_at DESC` and
   returned the first non-empty error, so a topic with an earlier
   failed run + a later completed run kept displaying the old
   error next to '1 completed'. Now the error only surfaces when
   the MOST RECENT run itself failed. Historical failures stay in
   the run count but don't leak their message.
This commit is contained in:
Omar Sobh
2026-07-16 15:59:04 -07:00
parent ae113dd319
commit e3ef3fd056
3 changed files with 22 additions and 5 deletions
+16 -4
View File
@@ -167,10 +167,22 @@ pub async fn pipeline_state(
.iter()
.filter(|r| r.try_get::<String, _>("status").ok().as_deref() == Some("completed"))
.count();
let latest_error = run_rows
.iter()
.find_map(|r| r.try_get::<Option<String>, _>("error").ok().flatten())
.filter(|s| !s.is_empty());
// 2026-07-16: only surface the error from the MOST RECENT run and
// only if that run itself failed. Previously we walked every run
// and returned the first non-empty error, so a pre-migration
// failed run's stale error kept showing next to a fresh successful
// run — reading like "everything is still broken" when it wasn't.
let latest_error = run_rows.first().and_then(|r| {
let status = r.try_get::<String, _>("status").ok();
if status.as_deref() == Some("failed") {
r.try_get::<Option<String>, _>("error")
.ok()
.flatten()
.filter(|s| !s.is_empty())
} else {
None
}
});
// Status rules:
// - 0 runs → skip (nothing to see yet — natural pre-fire state,
// NOT a failure)