← Back to task

Commit 46cfbc51

commit 46cfbc511229afd12b834df04790440fbc192379
Author: Ben Sima <ben@bensima.com>
Date:   Thu Nov 27 10:08:26 2025

    Replace back links with proper navbar
    
    The implementation is complete:
    
    1. **Created a shared `navbar` component** with Jr branding and
    navigati 2. **Replaced all back links** (`← Back to Dashboard`,
    `← Back to Tasks` 3. **Added navbar styling** in the Style.hs file,
    including:
       - Light mode styles - Dark mode styles - Mobile responsive styles
    4. **Build passes** with `bild --test Omni/Jr.hs`
    
    Task-Id: t-149.2

diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index 9d46a782..1ecc034b 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -114,6 +114,16 @@ pageHead title =
       ]
     Lucid.link_ [Lucid.rel_ "stylesheet", Lucid.href_ "/style.css"]
 
+navbar :: (Monad m) => Lucid.HtmlT m ()
+navbar =
+  Lucid.nav_ [Lucid.class_ "navbar"] <| do
+    Lucid.a_ [Lucid.href_ "/", Lucid.class_ "navbar-brand"] "Jr"
+    Lucid.div_ [Lucid.class_ "navbar-links"] <| do
+      Lucid.a_ [Lucid.href_ "/", Lucid.class_ "navbar-link"] "Dashboard"
+      Lucid.a_ [Lucid.href_ "/tasks", Lucid.class_ "navbar-link"] "Tasks"
+      Lucid.a_ [Lucid.href_ "/ready", Lucid.class_ "navbar-link"] "Ready"
+      Lucid.a_ [Lucid.href_ "/stats", Lucid.class_ "navbar-link"] "Stats"
+
 statusBadge :: (Monad m) => TaskCore.Status -> Lucid.HtmlT m ()
 statusBadge status =
   let (cls, label) = case status of
@@ -143,6 +153,7 @@ instance Lucid.ToHtml HomePage where
     Lucid.doctypehtml_ <| do
       pageHead "Jr Dashboard"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
           Lucid.h1_ "Jr Dashboard"
 
@@ -188,8 +199,8 @@ instance Lucid.ToHtml ReadyQueuePage where
     Lucid.doctypehtml_ <| do
       pageHead "Ready Queue - Jr"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/"] "← Back to Dashboard"
           Lucid.h1_ <| Lucid.toHtml ("Ready Queue (" <> tshow (length tasks) <> " tasks)")
           if null tasks
             then Lucid.p_ [Lucid.class_ "empty-msg"] "No tasks are ready for work."
@@ -201,8 +212,8 @@ instance Lucid.ToHtml TaskListPage where
     Lucid.doctypehtml_ <| do
       pageHead "Tasks - Jr"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/"] "← Back to Dashboard"
           Lucid.h1_ <| Lucid.toHtml ("Tasks (" <> tshow (length tasks) <> ")")
 
           Lucid.div_ [Lucid.class_ "filter-form"] <| do
@@ -265,20 +276,19 @@ instance Lucid.ToHtml TaskDetailPage where
     Lucid.doctypehtml_ <| do
       pageHead "Task Not Found - Jr"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
           Lucid.h1_ "Task Not Found"
           Lucid.p_ <| do
             "The task "
             Lucid.code_ (Lucid.toHtml tid)
             " could not be found."
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/tasks"] "← Back to Tasks"
   toHtml (TaskDetailFound task allTasks activities) =
     Lucid.doctypehtml_ <| do
       pageHead (TaskCore.taskId task <> " - Jr")
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/tasks"] "← Back to Tasks"
-
           Lucid.h1_ <| Lucid.toHtml (TaskCore.taskTitle task)
 
           Lucid.div_ [Lucid.class_ "task-detail"] <| do
@@ -439,20 +449,19 @@ instance Lucid.ToHtml TaskReviewPage where
     Lucid.doctypehtml_ <| do
       pageHead "Task Not Found - Jr Review"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
           Lucid.h1_ "Task Not Found"
           Lucid.p_ <| do
             "The task "
             Lucid.code_ (Lucid.toHtml tid)
             " could not be found."
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/tasks"] "← Back to Tasks"
   toHtml (ReviewPageFound task reviewInfo) =
     Lucid.doctypehtml_ <| do
       pageHead ("Review: " <> TaskCore.taskId task <> " - Jr")
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ ("/tasks/" <> TaskCore.taskId task)] "← Back to Task"
-
           Lucid.h1_ "Review Task"
 
           Lucid.div_ [Lucid.class_ "task-summary"] <| do
@@ -517,9 +526,8 @@ instance Lucid.ToHtml StatsPage where
     Lucid.doctypehtml_ <| do
       pageHead "Task Statistics - Jr"
       Lucid.body_ <| do
+        navbar
         Lucid.div_ [Lucid.class_ "container"] <| do
-          Lucid.p_ [Lucid.class_ "back-link"] <| Lucid.a_ [Lucid.href_ "/"] "← Back to Dashboard"
-
           Lucid.h1_ <| case maybeEpic of
             Nothing -> "Task Statistics"
             Just epicId -> Lucid.toHtml ("Statistics for Epic: " <> epicId)
diff --git a/Omni/Jr/Web/Style.hs b/Omni/Jr/Web/Style.hs
index 8b6a8a78..5aa9b623 100644
--- a/Omni/Jr/Web/Style.hs
+++ b/Omni/Jr/Web/Style.hs
@@ -142,6 +142,38 @@ layoutStyles = do
 
 navigationStyles :: Css
 navigationStyles = do
+  ".navbar" ? do
+    backgroundColor white
+    padding (px 12) (px 20) (px 12) (px 20)
+    boxShadow (NE.singleton (bsColor (rgba 0 0 0 0.08) (shadow (px 0) (px 2))))
+    marginBottom (px 16)
+    display flex
+    alignItems center
+    justifyContent spaceBetween
+    flexWrap Flexbox.wrap
+    Stylesheet.key "gap" ("12px" :: Text)
+  ".navbar-brand" ? do
+    fontSize (px 22)
+    fontWeight bold
+    color "#0066cc"
+    textDecoration none
+  ".navbar-brand" # hover ? textDecoration none
+  ".navbar-links" ? do
+    display flex
+    Stylesheet.key "gap" ("4px" :: Text)
+    flexWrap Flexbox.wrap
+  ".navbar-link" ? do
+    display inlineBlock
+    padding (px 8) (px 14) (px 8) (px 14)
+    color "#374151"
+    textDecoration none
+    borderRadius (px 6) (px 6) (px 6) (px 6)
+    fontSize (px 14)
+    fontWeight (weight 500)
+    transition "background-color" (ms 150) ease (sec 0)
+  ".navbar-link" # hover ? do
+    backgroundColor "#f3f4f6"
+    textDecoration none
   header ? do
     backgroundColor white
     padding (px 12) (px 16) (px 12) (px 16)
@@ -165,10 +197,6 @@ navigationStyles = do
     display flex
     Stylesheet.key "gap" ("8px" :: Text)
     flexWrap Flexbox.wrap
-  ".back-link" ? do
-    display inlineBlock
-    marginBottom (em 0.75)
-    fontSize (px 14)
   ".actions" ? do
     display flex
     flexWrap Flexbox.wrap
@@ -530,6 +558,13 @@ responsiveStyles = do
   query Media.screen [Media.maxWidth (px 600)] <| do
     body ? fontSize (px 15)
     ".container" ? padding (px 12) (px 12) (px 12) (px 12)
+    ".navbar" ? do
+      flexDirection column
+      alignItems flexStart
+      padding (px 12) (px 12) (px 12) (px 12)
+    ".navbar-links" ? do
+      width (pct 100)
+      justifyContent center
     ".nav-content" ? do
       flexDirection column
       alignItems flexStart
@@ -572,6 +607,12 @@ darkModeStyles =
     header ? do
       backgroundColor "#1f2937"
       boxShadow (NE.singleton (bsColor (rgba 0 0 0 0.3) (shadow (px 0) (px 2))))
+    ".navbar" ? do
+      backgroundColor "#1f2937"
+      boxShadow (NE.singleton (bsColor (rgba 0 0 0 0.3) (shadow (px 0) (px 2))))
+    ".navbar-brand" ? color "#60a5fa"
+    ".navbar-link" ? color "#d1d5db"
+    ".navbar-link" # hover ? backgroundColor "#374151"
     ".nav-brand" ? color "#f3f4f6"
     "h2" <> "h3" ? color "#d1d5db"
     a ? color "#60a5fa"