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
This commit is contained in:
2026-07-16 07:43:38 -04:00
parent 95ac550191
commit dbfe3a7c66
11 changed files with 42 additions and 27 deletions
+8 -3
View File
@@ -21,7 +21,12 @@ Hyperbole, a serverside web framework. There is zero application JavaScript.
## Haskell Conventions ## Haskell Conventions
- **Style:** fourmolu-formatted. Run `./hs fourmolu --mode inplace app/ src/ test/` before committing. - **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. - **Warnings:** `-Wall -Werror` in `package.yaml`. All warnings are fatal.
- **Module qualifiers:** Use qualified imports with descriptive aliases - **Module qualifiers:** Use qualified imports with descriptive aliases
(e.g., `import Data.Text qualified as T`). (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 - **Framework:** [Hyperbole](https://github.com/seanhess/hyperbole) — Haskell
serverside web framework. All HTML rendered in Haskell. serverside web framework. All HTML rendered in Haskell.
- **CSS:** [Neo Brutalism](https://unpkg.com/neobrutalismcss@latest) CDN + - **CSS:** [Neo Brutalism](https://github.com/matifandy8/NeoBrutalismCSS) CDN via jsdelivr + `frontend/static/style.css` for custom styles.
`frontend/static/style.css` for custom styles.
- **Build:** No npm/build step for frontend. All pages rendered in Haskell. - **Build:** No npm/build step for frontend. All pages rendered in Haskell.
- **Interactive components:** HyperViews with typed Actions and server-side - **Interactive components:** HyperViews with typed Actions and server-side
updates via VirtualDOM over WebSocket. updates via VirtualDOM over WebSocket.
@@ -86,5 +90,6 @@ sis/
- Run `./scripts/test` before committing. Tests must pass. - Run `./scripts/test` before committing. Tests must pass.
## Agent Autonomy ## Agent Autonomy
- Run `./hs hlint app/ src/ test/` and fix ALL hints before committing.
- As changes are completed then verify functionality using Playwright - As changes are completed then verify functionality using Playwright
- Once functionality is confirmed then commit and push changes - Once functionality is confirmed then commit and push changes
+1 -1
View File
@@ -17,7 +17,7 @@ import Effectful
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
+2 -2
View File
@@ -18,7 +18,7 @@ import Effectful
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
@@ -86,7 +86,7 @@ scheduleLabel ScheduleSometime = "Sometime"
scheduleLabel (ScheduleOneOff d _) = "One-off on " <> T.pack (show d) scheduleLabel (ScheduleOneOff d _) = "One-off on " <> T.pack (show d)
scheduleLabel (ScheduleRecurring p _ mt _ _) = scheduleLabel (ScheduleRecurring p _ mt _ _) =
let pText = case p of PeriodDaily -> "daily"; PeriodWeekly -> "weekly"; PeriodMonthly -> "monthly" 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 in "Recurs " <> pText <> timePart
page :: (Hyperbole :> es, DB :> es, IOE :> es) => Page es '[ChoresPage] page :: (Hyperbole :> es, DB :> es, IOE :> es) => Page es '[ChoresPage]
+1 -1
View File
@@ -19,7 +19,7 @@ import Effectful
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
+3 -3
View File
@@ -17,7 +17,7 @@ import Effectful
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
@@ -43,7 +43,7 @@ instance (DB :> es, IOE :> es) => HyperView HouseholdPage es where
Just us -> do Just us -> do
hhs <- getUserHouseholds (UserId (usUserId us)) hhs <- getUserHouseholds (UserId (usUserId us))
case hhs of case hhs of
[] -> pure (noHouseholdView) [] -> pure noHouseholdView
(h : _) -> do (h : _) -> do
let hid = unHouseholdId (householdId h) let hid = unHouseholdId (householdId h)
mems <- getMembers hid mems <- getMembers hid
@@ -123,7 +123,7 @@ page = do
Just us -> do Just us -> do
hhs <- getUserHouseholds (UserId (usUserId us)) hhs <- getUserHouseholds (UserId (usUserId us))
case hhs of case hhs of
[] -> pure $ hyper HouseholdPage $ noHouseholdView [] -> pure $ hyper HouseholdPage noHouseholdView
(h : _) -> do (h : _) -> do
let hid = unHouseholdId (householdId h) let hid = unHouseholdId (householdId h)
mems <- getMembers hid mems <- getMembers hid
+2 -2
View File
@@ -17,7 +17,7 @@ import Effectful
import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Auth (generateToken, hashPassword, verifyPassword)
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
@@ -41,7 +41,7 @@ instance (DB :> es, IOE :> es) => HyperView LoginPage es where
Just u Just u
| verifyPassword (lfPassword formData') (userPasswordHash u) -> do | verifyPassword (lfPassword formData') (userPasswordHash u) -> do
saveSession (UserSession (unUserId (userId u))) saveSession (UserSession (unUserId (userId u)))
pure (loginSuccessView) pure loginSuccessView
_ -> pure (loginView (Just "Invalid email or password")) _ -> pure (loginView (Just "Invalid email or password"))
update Noop = pure (loginView Nothing) update Noop = pure (loginView Nothing)
+2 -2
View File
@@ -18,7 +18,7 @@ import Effectful
import Sis.Auth (generateToken, hashPassword, verifyPassword) import Sis.Auth (generateToken, hashPassword, verifyPassword)
import Sis.Database import Sis.Database
import Sis.Route 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.Types
import Sis.View.Layout import Sis.View.Layout
import Web.Hyperbole import Web.Hyperbole
@@ -49,7 +49,7 @@ instance (DB :> es, IOE :> es) => HyperView SignupPage es where
pwHash <- liftIO (hashPassword (sfPassword form)) pwHash <- liftIO (hashPassword (sfPassword form))
uid <- createUser (sfDisplayName form) (sfEmail form) pwHash uid <- createUser (sfDisplayName form) (sfEmail form) pwHash
saveSession (UserSession (unUserId uid)) saveSession (UserSession (unUserId uid))
pure (signupSuccessView) pure signupSuccessView
signupSuccessView :: View SignupPage () signupSuccessView :: View SignupPage ()
signupSuccessView = do signupSuccessView = do
-1
View File
@@ -1,5 +1,4 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Sis.Route (AppRoute (..)) where module Sis.Route (AppRoute (..)) where
+18 -8
View File
@@ -1,13 +1,23 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
-- | Neo Brutalism CSS class name constants for Hyperbole views. {- | Neo Brutalism CSS class name constants for Hyperbole views.
-- Based on https://github.com/matifandy8/NeoBrutalismCSS Based on https://github.com/matifandy8/NeoBrutalismCSS
module Sis.Style -}
( nbBoxClass, nbButtonDefaultClass, nbInputClass, nbLabelClass module Sis.Style (
, nbBadgeClass, nbHeadingClass nbBoxClass,
, nbNavbarClass, nbNavbarLinkClass, nbContainerClass, nbListItemClass nbButtonDefaultClass,
, colorRed, colorYellow, colorGreen nbInputClass,
) where nbLabelClass,
nbBadgeClass,
nbHeadingClass,
nbNavbarClass,
nbNavbarLinkClass,
nbContainerClass,
nbListItemClass,
colorRed,
colorYellow,
colorGreen,
) where
import Data.Text (Text) import Data.Text (Text)
+3 -2
View File
@@ -292,7 +292,8 @@ data ActivityFormData = ActivityFormData
} }
deriving (Show, Eq, Generic, FromForm) deriving (Show, Eq, Generic, FromForm)
data HouseholdFormData = HouseholdFormData newtype HouseholdFormData = HouseholdFormData
{ hfdName :: Text { hfdName :: Text
} }
deriving (Show, Eq, Generic, FromForm) deriving stock (Show, Eq, Generic)
deriving anyclass (FromForm)
+2 -2
View File
@@ -15,7 +15,7 @@ import Data.Text (Text)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Sis.Route import Sis.Route
import Sis.Style (nbNavbarClass, nbNavbarLinkClass, nbHeadingClass, nbButtonDefaultClass) import Sis.Style (nbButtonDefaultClass, nbHeadingClass, nbNavbarClass, nbNavbarLinkClass)
import Web.Hyperbole import Web.Hyperbole
import Web.Hyperbole.Effect.Session import Web.Hyperbole.Effect.Session
@@ -23,7 +23,7 @@ import Web.Hyperbole.Effect.Session
-- Session -- Session
---------------------------------------------------------------------- ----------------------------------------------------------------------
data UserSession = UserSession newtype UserSession = UserSession
{ usUserId :: Int { usUserId :: Int
} }
deriving stock (Show, Eq, Generic) deriving stock (Show, Eq, Generic)