← Back to task

Commit 64504c1c

commit 64504c1cd5aba7f0ba31e4d6451bbf992a72b8f9
Author: Ben Sima <ben@bensima.com>
Date:   Thu Nov 27 14:02:59 2025

    Display retry context on task detail page
    
    The build and tests pass. The retry context display is already
    implement
    
    The implementation includes: - Current attempt number (e.g., "Attempt
    3 of 3") at line 721 - Failure reason with `summarizeReason` at lines
    696-697 - Original commit display at lines 699-703 - Conflict files
    list at lines 705-710 - Warning banner when max retries exceeded at
    lines 691-692 and 712-715
    
    Task-Id: t-153.3

diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index b751ee99..6d0fa200 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -429,6 +429,8 @@ instance Lucid.ToHtml TaskDetailPage where
         Lucid.div_ [Lucid.class_ "container"] <| do
           Lucid.h1_ <| Lucid.toHtml (TaskCore.taskTitle task)
 
+          renderRetryContextBanner maybeRetry
+
           Lucid.div_ [Lucid.class_ "task-detail"] <| do
             Lucid.div_ [Lucid.class_ "detail-row"] <| do
               Lucid.span_ [Lucid.class_ "detail-label"] "ID:"
@@ -679,6 +681,52 @@ instance Lucid.ToHtml TaskDetailPage where
             let dollars = fromIntegral cents / 100.0 :: Double
              in "$" <> Text.pack (showFFloat (Just 2) dollars "")
 
+renderRetryContextBanner :: (Monad m) => Maybe TaskCore.RetryContext -> Lucid.HtmlT m ()
+renderRetryContextBanner Nothing = pure ()
+renderRetryContextBanner (Just ctx) =
+  Lucid.div_ [Lucid.class_ bannerClass] <| do
+    Lucid.div_ [Lucid.class_ "retry-banner-header"] <| do
+      Lucid.span_ [Lucid.class_ "retry-icon"] retryIcon
+      Lucid.span_ [Lucid.class_ "retry-attempt"] (Lucid.toHtml attemptText)
+      when maxRetriesExceeded
+        <| Lucid.span_ [Lucid.class_ "retry-warning-badge"] "Needs Human Intervention"
+
+    Lucid.div_ [Lucid.class_ "retry-banner-details"] <| do
+      Lucid.div_ [Lucid.class_ "retry-detail-row"] <| do
+        Lucid.span_ [Lucid.class_ "retry-label"] "Failure Reason:"
+        Lucid.span_ [Lucid.class_ "retry-value"] (Lucid.toHtml (summarizeReason (TaskCore.retryReason ctx)))
+
+      let commit = TaskCore.retryOriginalCommit ctx
+      unless (Text.null commit) <| do
+        Lucid.div_ [Lucid.class_ "retry-detail-row"] <| do
+          Lucid.span_ [Lucid.class_ "retry-label"] "Original Commit:"
+          Lucid.code_ [Lucid.class_ "retry-commit"] (Lucid.toHtml (Text.take 8 commit))
+
+      let conflicts = TaskCore.retryConflictFiles ctx
+      unless (null conflicts) <| do
+        Lucid.div_ [Lucid.class_ "retry-detail-row"] <| do
+          Lucid.span_ [Lucid.class_ "retry-label"] "Conflict Files:"
+          Lucid.ul_ [Lucid.class_ "retry-conflict-list"]
+            <| traverse_ (Lucid.li_ <. Lucid.toHtml) conflicts
+
+    when maxRetriesExceeded
+      <| Lucid.div_
+        [Lucid.class_ "retry-warning-message"]
+        "This task has exceeded the maximum number of retries. A human must review the failure and either fix the issue manually or reset the retry count."
+  where
+    attempt = TaskCore.retryAttempt ctx
+    maxRetriesExceeded = attempt >= 3
+    bannerClass = if maxRetriesExceeded then "retry-banner retry-banner-critical" else "retry-banner retry-banner-warning"
+    retryIcon = if maxRetriesExceeded then "⚠" else "↻"
+    attemptText = "Attempt " <> tshow attempt <> " of 3"
+
+    summarizeReason :: Text -> Text
+    summarizeReason reason
+      | "rejected:" `Text.isPrefixOf` reason = "Rejected: " <> Text.strip (Text.drop 9 reason)
+      | "Test failure:" `Text.isPrefixOf` reason = "Test failure (see details below)"
+      | "MERGE CONFLICT" `Text.isPrefixOf` reason = "Merge conflict with concurrent changes"
+      | otherwise = Text.take 100 reason <> if Text.length reason > 100 then "..." else ""
+
 instance Lucid.ToHtml TaskReviewPage where
   toHtmlRaw = Lucid.toHtml
   toHtml (ReviewPageNotFound tid) =
diff --git a/Omni/Jr/Web/Style.hs b/Omni/Jr/Web/Style.hs
index 19fc3710..9ec03d6b 100644
--- a/Omni/Jr/Web/Style.hs
+++ b/Omni/Jr/Web/Style.hs
@@ -32,6 +32,7 @@ stylesheet = do
   activityTimelineStyles
   commitStyles
   markdownStyles
+  retryBannerStyles
   responsiveStyles
   darkModeStyles
 
@@ -706,6 +707,74 @@ markdownStyles = do
     padding (px 1) (px 4) (px 1) (px 4)
     borderRadius (px 2) (px 2) (px 2) (px 2)
 
+retryBannerStyles :: Css
+retryBannerStyles = do
+  ".retry-banner" ? do
+    borderRadius (px 4) (px 4) (px 4) (px 4)
+    padding (px 12) (px 16) (px 12) (px 16)
+    margin (px 0) (px 0) (px 16) (px 0)
+  ".retry-banner-warning" ? do
+    backgroundColor "#fef3c7"
+    border (px 1) solid "#f59e0b"
+  ".retry-banner-critical" ? do
+    backgroundColor "#fee2e2"
+    border (px 1) solid "#ef4444"
+  ".retry-banner-header" ? do
+    display flex
+    alignItems center
+    Stylesheet.key "gap" ("8px" :: Text)
+    marginBottom (px 8)
+  ".retry-icon" ? do
+    fontSize (px 18)
+    fontWeight bold
+  ".retry-attempt" ? do
+    fontSize (px 14)
+    fontWeight (weight 600)
+    color "#374151"
+  ".retry-warning-badge" ? do
+    backgroundColor "#dc2626"
+    color white
+    fontSize (px 11)
+    fontWeight (weight 600)
+    padding (px 2) (px 8) (px 2) (px 8)
+    borderRadius (px 2) (px 2) (px 2) (px 2)
+    marginLeft auto
+  ".retry-banner-details" ? do
+    fontSize (px 13)
+    color "#374151"
+  ".retry-detail-row" ? do
+    display flex
+    alignItems flexStart
+    Stylesheet.key "gap" ("8px" :: Text)
+    margin (px 4) (px 0) (px 4) (px 0)
+  ".retry-label" ? do
+    fontWeight (weight 500)
+    minWidth (px 110)
+    flexShrink 0
+  ".retry-value" ? do
+    color "#4b5563"
+  ".retry-commit" ? do
+    fontFamily ["SF Mono", "Monaco", "Consolas", "monospace"] [monospace]
+    fontSize (em 0.9)
+    backgroundColor "#f3f4f6"
+    padding (px 1) (px 4) (px 1) (px 4)
+    borderRadius (px 2) (px 2) (px 2) (px 2)
+  ".retry-conflict-list" ? do
+    margin (px 0) (px 0) (px 0) (px 0)
+    padding (px 0) (px 0) (px 0) (px 16)
+  (".retry-conflict-list" ** li) ? do
+    fontFamily ["SF Mono", "Monaco", "Consolas", "monospace"] [monospace]
+    fontSize (px 12)
+    margin (px 2) (px 0) (px 2) (px 0)
+  ".retry-warning-message" ? do
+    marginTop (px 12)
+    padding (px 10) (px 12) (px 10) (px 12)
+    backgroundColor "#fecaca"
+    borderRadius (px 2) (px 2) (px 2) (px 2)
+    fontSize (px 12)
+    color "#991b1b"
+    fontWeight (weight 500)
+
 responsiveStyles :: Css
 responsiveStyles = do
   query Media.screen [Media.maxWidth (px 600)] <| do