From dbfe3a7c6660e37c9cbb0f82e20b8ace66b438fc Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Thu, 16 Jul 2026 07:43:38 -0400 Subject: [PATCH] chore: fix all hlint warnings, update AGENTS.md pre-commit checklist - Fix 8 hlint hints across 6 files (unused pragma, newtype, lambda, redundant brackets/\$) - Add blank line after LANGUAGE pragma in Route.hs (fourmolu) - Fix HouseholdFormData deriving to use explicit strategies for newtype - Update AGENTS.md: require hlint clean before every commit, add pre-commit checklist (format, lint, build, test), update NB CSS URL to jsdelivr CDN --- AGENTS.md | 11 ++++++++--- src/Sis/Page/Activity.hs | 2 +- src/Sis/Page/Chores.hs | 4 ++-- src/Sis/Page/Dashboard.hs | 2 +- src/Sis/Page/Household.hs | 6 +++--- src/Sis/Page/Login.hs | 4 ++-- src/Sis/Page/Signup.hs | 4 ++-- src/Sis/Route.hs | 1 - src/Sis/Style.hs | 26 ++++++++++++++++++-------- src/Sis/Types.hs | 5 +++-- src/Sis/View/Layout.hs | 4 ++-- 11 files changed, 42 insertions(+), 27 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b2a44a8..f3c9893 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,12 @@ Hyperbole, a serverside web framework. There is zero application JavaScript. ## Haskell Conventions - **Style:** fourmolu-formatted. Run `./hs fourmolu --mode inplace app/ src/ test/` before committing. -- **Lint:** hlint clean required. Fix any hints before committing. +- **Lint:** hlint clean required — MUST run `./hs hlint app/ src/ test/` and fix all hints before committing. Zero hints is the standard. Do not suppress or ignore hlint suggestions. +- **Pre-commit checklist:** Before every commit, run: + 1. `./hs fourmolu --mode inplace app/ src/ test/` — format code + 2. `./hs hlint app/ src/ test/` — fix ALL hints (must output "No hints") + 3. `./hs stack build --fast` — must compile with zero errors + 4. `./hs stack test --fast` — all tests must pass - **Warnings:** `-Wall -Werror` in `package.yaml`. All warnings are fatal. - **Module qualifiers:** Use qualified imports with descriptive aliases (e.g., `import Data.Text qualified as T`). @@ -37,8 +42,7 @@ Hyperbole, a serverside web framework. There is zero application JavaScript. - **Framework:** [Hyperbole](https://github.com/seanhess/hyperbole) — Haskell serverside web framework. All HTML rendered in Haskell. -- **CSS:** [Neo Brutalism](https://unpkg.com/neobrutalismcss@latest) CDN + - `frontend/static/style.css` for custom styles. +- **CSS:** [Neo Brutalism](https://github.com/matifandy8/NeoBrutalismCSS) CDN via jsdelivr + `frontend/static/style.css` for custom styles. - **Build:** No npm/build step for frontend. All pages rendered in Haskell. - **Interactive components:** HyperViews with typed Actions and server-side updates via VirtualDOM over WebSocket. @@ -86,5 +90,6 @@ sis/ - Run `./scripts/test` before committing. Tests must pass. ## Agent Autonomy +- Run `./hs hlint app/ src/ test/` and fix ALL hints before committing. - As changes are completed then verify functionality using Playwright - Once functionality is confirmed then commit and push changes diff --git a/src/Sis/Page/Activity.hs b/src/Sis/Page/Activity.hs index f61b452..8238189 100644 --- a/src/Sis/Page/Activity.hs +++ b/src/Sis/Page/Activity.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole diff --git a/src/Sis/Page/Chores.hs b/src/Sis/Page/Chores.hs index 61d819e..b0ff0dd 100644 --- a/src/Sis/Page/Chores.hs +++ b/src/Sis/Page/Chores.hs @@ -18,7 +18,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -86,7 +86,7 @@ scheduleLabel ScheduleSometime = "Sometime" scheduleLabel (ScheduleOneOff d _) = "One-off on " <> T.pack (show d) scheduleLabel (ScheduleRecurring p _ mt _ _) = let pText = case p of PeriodDaily -> "daily"; PeriodWeekly -> "weekly"; PeriodMonthly -> "monthly" - timePart = maybe "" (\t -> " at " <> t) mt + timePart = maybe "" (" at " <>) mt in "Recurs " <> pText <> timePart page :: (Hyperbole :> es, DB :> es, IOE :> es) => Page es '[ChoresPage] diff --git a/src/Sis/Page/Dashboard.hs b/src/Sis/Page/Dashboard.hs index bcc9333..1f318bd 100644 --- a/src/Sis/Page/Dashboard.hs +++ b/src/Sis/Page/Dashboard.hs @@ -19,7 +19,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole diff --git a/src/Sis/Page/Household.hs b/src/Sis/Page/Household.hs index ca1a7e8..dc2453a 100644 --- a/src/Sis/Page/Household.hs +++ b/src/Sis/Page/Household.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -43,7 +43,7 @@ instance (DB :> es, IOE :> es) => HyperView HouseholdPage es where Just us -> do hhs <- getUserHouseholds (UserId (usUserId us)) case hhs of - [] -> pure (noHouseholdView) + [] -> pure noHouseholdView (h : _) -> do let hid = unHouseholdId (householdId h) mems <- getMembers hid @@ -123,7 +123,7 @@ page = do Just us -> do hhs <- getUserHouseholds (UserId (usUserId us)) case hhs of - [] -> pure $ hyper HouseholdPage $ noHouseholdView + [] -> pure $ hyper HouseholdPage noHouseholdView (h : _) -> do let hid = unHouseholdId (householdId h) mems <- getMembers hid diff --git a/src/Sis/Page/Login.hs b/src/Sis/Page/Login.hs index c024fdc..2a58151 100644 --- a/src/Sis/Page/Login.hs +++ b/src/Sis/Page/Login.hs @@ -17,7 +17,7 @@ import Effectful import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -41,7 +41,7 @@ instance (DB :> es, IOE :> es) => HyperView LoginPage es where Just u | verifyPassword (lfPassword formData') (userPasswordHash u) -> do saveSession (UserSession (unUserId (userId u))) - pure (loginSuccessView) + pure loginSuccessView _ -> pure (loginView (Just "Invalid email or password")) update Noop = pure (loginView Nothing) diff --git a/src/Sis/Page/Signup.hs b/src/Sis/Page/Signup.hs index ed144cb..ddea543 100644 --- a/src/Sis/Page/Signup.hs +++ b/src/Sis/Page/Signup.hs @@ -18,7 +18,7 @@ import Effectful import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Database import Sis.Route -import Sis.Style (nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass, nbBadgeClass, nbHeadingClass, nbContainerClass, nbListItemClass, colorRed, colorYellow, colorGreen) +import Sis.Style (colorGreen, colorRed, colorYellow, nbBadgeClass, nbBoxClass, nbButtonDefaultClass, nbContainerClass, nbHeadingClass, nbInputClass, nbLabelClass, nbListItemClass) import Sis.Types import Sis.View.Layout import Web.Hyperbole @@ -49,7 +49,7 @@ instance (DB :> es, IOE :> es) => HyperView SignupPage es where pwHash <- liftIO (hashPassword (sfPassword form)) uid <- createUser (sfDisplayName form) (sfEmail form) pwHash saveSession (UserSession (unUserId uid)) - pure (signupSuccessView) + pure signupSuccessView signupSuccessView :: View SignupPage () signupSuccessView = do diff --git a/src/Sis/Route.hs b/src/Sis/Route.hs index e2445e3..2f44467 100644 --- a/src/Sis/Route.hs +++ b/src/Sis/Route.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE OverloadedStrings #-} module Sis.Route (AppRoute (..)) where diff --git a/src/Sis/Style.hs b/src/Sis/Style.hs index 8182259..f8f6f10 100644 --- a/src/Sis/Style.hs +++ b/src/Sis/Style.hs @@ -1,13 +1,23 @@ {-# LANGUAGE OverloadedStrings #-} --- | Neo Brutalism CSS class name constants for Hyperbole views. --- Based on https://github.com/matifandy8/NeoBrutalismCSS -module Sis.Style - ( nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass - , nbBadgeClass, nbHeadingClass - , nbNavbarClass, nbNavbarLinkClass, nbContainerClass, nbListItemClass - , colorRed, colorYellow, colorGreen - ) where +{- | Neo Brutalism CSS class name constants for Hyperbole views. +Based on https://github.com/matifandy8/NeoBrutalismCSS +-} +module Sis.Style ( + nbBoxClass, + nbButtonDefaultClass, + nbInputClass, + nbLabelClass, + nbBadgeClass, + nbHeadingClass, + nbNavbarClass, + nbNavbarLinkClass, + nbContainerClass, + nbListItemClass, + colorRed, + colorYellow, + colorGreen, +) where import Data.Text (Text) diff --git a/src/Sis/Types.hs b/src/Sis/Types.hs index 82b0476..5e0ce36 100644 --- a/src/Sis/Types.hs +++ b/src/Sis/Types.hs @@ -292,7 +292,8 @@ data ActivityFormData = ActivityFormData } deriving (Show, Eq, Generic, FromForm) -data HouseholdFormData = HouseholdFormData +newtype HouseholdFormData = HouseholdFormData { hfdName :: Text } - deriving (Show, Eq, Generic, FromForm) + deriving stock (Show, Eq, Generic) + deriving anyclass (FromForm) diff --git a/src/Sis/View/Layout.hs b/src/Sis/View/Layout.hs index a107f7d..acc2fcb 100644 --- a/src/Sis/View/Layout.hs +++ b/src/Sis/View/Layout.hs @@ -15,7 +15,7 @@ import Data.Text (Text) import GHC.Generics (Generic) import Sis.Route -import Sis.Style (nbNavbarClass, nbNavbarLinkClass, nbHeadingClass, nbButtonDefaultClass) +import Sis.Style (nbButtonDefaultClass, nbHeadingClass, nbNavbarClass, nbNavbarLinkClass) import Web.Hyperbole import Web.Hyperbole.Effect.Session @@ -23,7 +23,7 @@ import Web.Hyperbole.Effect.Session -- Session ---------------------------------------------------------------------- -data UserSession = UserSession +newtype UserSession = UserSession { usUserId :: Int } deriving stock (Show, Eq, Generic)