From 6eb6e6693a27c9450be4963c0d2043c88e2c5edb Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Thu, 27 Nov 2025 13:53:59 -0500 Subject: Add diff view page for commits All the pieces are in place: 1. Route: `GET /tasks/:id/diff/:commit` (line 65) 2. Data type: `TaskDiffPage` (line 119) 3. `ToHtml` instance for rendering (line 759) 4. Handler: `taskDiffHandler` (line 1225) 5. Helper: `getDiffForCommit` (line 1301) The implementation shows: - Full git diff output via `git show ` - Pre-formatted output (`
`) - Link
back to task detail page - Proper error handling for invalid commits

Task-Id: t-153.2
---
 Omni/Jr/Web.hs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

(limited to 'Omni')

diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index 55ff06b..b751ee9 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -62,6 +62,7 @@ type API =
     :<|> "tasks" :> Capture "id" Text :> "status" :> ReqBody '[FormUrlEncoded] StatusForm :> Post '[Lucid.HTML] StatusBadgePartial
     :<|> "tasks" :> Capture "id" Text :> "description" :> ReqBody '[FormUrlEncoded] DescriptionForm :> PostRedirect
     :<|> "tasks" :> Capture "id" Text :> "review" :> Get '[Lucid.HTML] TaskReviewPage
+    :<|> "tasks" :> Capture "id" Text :> "diff" :> Capture "commit" Text :> Get '[Lucid.HTML] TaskDiffPage
     :<|> "tasks" :> Capture "id" Text :> "accept" :> PostRedirect
     :<|> "tasks" :> Capture "id" Text :> "reject" :> ReqBody '[FormUrlEncoded] RejectForm :> PostRedirect
     :<|> "partials" :> "recent-activity" :> Get '[Lucid.HTML] RecentActivityPartial
@@ -115,6 +116,10 @@ data ReviewInfo
   | ReviewMergeConflict Text [Text]
   | ReviewReady Text Text
 
+data TaskDiffPage
+  = DiffPageFound Text Text Text
+  | DiffPageNotFound Text Text
+
 data StatsPage = StatsPage TaskCore.TaskStats (Maybe Text)
 
 newtype RecentActivityPartial = RecentActivityPartial [TaskCore.Task]
@@ -751,6 +756,32 @@ instance Lucid.ToHtml TaskReviewPage where
                       ""
                     Lucid.button_ [Lucid.type_ "submit", Lucid.class_ "reject-btn"] "Reject"
 
+instance Lucid.ToHtml TaskDiffPage where
+  toHtmlRaw = Lucid.toHtml
+  toHtml (DiffPageNotFound tid commitHash') =
+    Lucid.doctypehtml_ <| do
+      pageHead "Commit Not Found - Jr"
+      Lucid.body_ <| do
+        navbar
+        Lucid.div_ [Lucid.class_ "container"] <| do
+          Lucid.h1_ "Commit Not Found"
+          Lucid.p_ <| do
+            "Could not find commit "
+            Lucid.code_ (Lucid.toHtml commitHash')
+          Lucid.a_ [Lucid.href_ ("/tasks/" <> tid), Lucid.class_ "back-link"] "← Back to task"
+  toHtml (DiffPageFound tid commitHash' diffOutput) =
+    Lucid.doctypehtml_ <| do
+      pageHead ("Diff " <> Text.take 8 commitHash' <> " - Jr")
+      Lucid.body_ <| do
+        navbar
+        Lucid.div_ [Lucid.class_ "container"] <| do
+          Lucid.div_ [Lucid.class_ "diff-header"] <| do
+            Lucid.a_ [Lucid.href_ ("/tasks/" <> tid), Lucid.class_ "back-link"] "← Back to task"
+            Lucid.h1_ <| do
+              "Commit "
+              Lucid.code_ (Lucid.toHtml (Text.take 8 commitHash'))
+          Lucid.pre_ [Lucid.class_ "diff-block"] (Lucid.toHtml diffOutput)
+
 instance Lucid.ToHtml StatsPage where
   toHtmlRaw = Lucid.toHtml
   toHtml (StatsPage stats maybeEpic) =
@@ -1076,6 +1107,7 @@ server =
     :<|> taskStatusHandler
     :<|> taskDescriptionHandler
     :<|> taskReviewHandler
+    :<|> taskDiffHandler
     :<|> taskAcceptHandler
     :<|> taskRejectHandler
     :<|> recentActivityHandler
@@ -1190,6 +1222,13 @@ server =
           reviewInfo <- liftIO <| getReviewInfo tid
           pure (ReviewPageFound task reviewInfo)
 
+    taskDiffHandler :: Text -> Text -> Servant.Handler TaskDiffPage
+    taskDiffHandler tid commitSha = do
+      diffOutput <- liftIO <| getDiffForCommit commitSha
+      case diffOutput of
+        Nothing -> pure (DiffPageNotFound tid commitSha)
+        Just output -> pure (DiffPageFound tid commitSha output)
+
     taskAcceptHandler :: Text -> Servant.Handler (Headers '[Header "Location" Text] NoContent)
     taskAcceptHandler tid = do
       liftIO <| do
@@ -1259,6 +1298,17 @@ getReviewInfo tid = do
               ""
           pure (ReviewReady commitSha (Text.pack diffOut))
 
+getDiffForCommit :: Text -> IO (Maybe Text)
+getDiffForCommit commitSha = do
+  (code, diffOut, _) <-
+    Process.readProcessWithExitCode
+      "git"
+      ["show", Text.unpack commitSha]
+      ""
+  case code of
+    Exit.ExitSuccess -> pure (Just (Text.pack diffOut))
+    Exit.ExitFailure _ -> pure Nothing
+
 findCommitForTask :: Text -> IO (Maybe Text)
 findCommitForTask tid = do
   let grepArg = "--grep=" <> Text.unpack tid
-- 
cgit v1.2.3