diff --git a/Web/Controller/AiGovernancePolicies.hs b/Web/Controller/AiGovernancePolicies.hs
index 38c61b8..0da13ea 100644
--- a/Web/Controller/AiGovernancePolicies.hs
+++ b/Web/Controller/AiGovernancePolicies.hs
@@ -6,6 +6,7 @@ import Web.Controller.Prelude
import Web.View.AiGovernancePolicies.Index
import Web.View.AiGovernancePolicies.New
import Application.Helper.AgentBridge (jsonArrayTexts)
+import qualified Data.Aeson as A
validAllowedActions :: [Text]
validAllowedActions = ["read", "propose", "delegate", "auto_apply"]
diff --git a/Web/Controller/Api/V2/Token.hs b/Web/Controller/Api/V2/Token.hs
index 2a3c409..6d28393 100644
--- a/Web/Controller/Api/V2/Token.hs
+++ b/Web/Controller/Api/V2/Token.hs
@@ -10,6 +10,7 @@ import IHP.ControllerPrelude
import Data.Aeson (object, (.=))
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
+import qualified Data.UUID as UUID
import qualified "cryptohash-sha256" Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Random as Random
@@ -50,7 +51,7 @@ instance Controller ApiV2TokenController where
]
(Just cid, Just csec) -> do
-- Look up consumer by id
- case readMay cid of
+ case UUID.fromText cid of
Nothing -> respondWithStatus 400 $ object
["error" .= ("invalid_client" :: Text)]
Just rawId -> do
diff --git a/Web/Controller/CollectiveProposals.hs b/Web/Controller/CollectiveProposals.hs
index c0ffe02..adb1c63 100644
--- a/Web/Controller/CollectiveProposals.hs
+++ b/Web/Controller/CollectiveProposals.hs
@@ -8,7 +8,7 @@ import Web.View.CollectiveProposals.Index
import Web.View.CollectiveProposals.Show
import Application.Helper.AgentBridge (callAgent, callAgentsBatch, BridgeResponse(..))
import Application.Helper.ModelRouter (resolveAllAgents)
-import Data.List (intercalate)
+import qualified Data.Aeson as A
instance Controller CollectiveProposalsController where
diff --git a/Web/Controller/HubRoutingRules.hs b/Web/Controller/HubRoutingRules.hs
index 9ca04fa..a5b7703 100644
--- a/Web/Controller/HubRoutingRules.hs
+++ b/Web/Controller/HubRoutingRules.hs
@@ -42,8 +42,8 @@ instance Controller HubRoutingRulesController where
|> fill @'["sourceHubId","targetHubId","matchCategory","matchWidgetType","priority","notes"]
|> validateField #sourceHubId nonEmpty
|> validateField #targetHubId nonEmpty
- |> (case wtResult of { Left msg -> attachFailure #matchWidgetType msg; Right () -> id })
- |> (case catResult of { Left msg -> attachFailure #matchCategory msg; Right () -> id })
+ |> (case wtResult of { Left msg -> attachFailure #matchWidgetType msg; Right () -> \x -> x })
+ |> (case catResult of { Left msg -> attachFailure #matchCategory msg; Right () -> \x -> x })
|> ifValid \case
Left r -> render NewView { rule = r, hubs }
Right r -> do
@@ -65,8 +65,8 @@ instance Controller HubRoutingRulesController where
catResult <- case mMatchCategory of { Nothing -> pure (Right ()); Just "" -> pure (Right ()); Just c -> liftIO (validateAnnotationCategory c) }
rule
|> fill @'["matchCategory","matchWidgetType","priority","notes"]
- |> (case wtResult of { Left msg -> attachFailure #matchWidgetType msg; Right () -> id })
- |> (case catResult of { Left msg -> attachFailure #matchCategory msg; Right () -> id })
+ |> (case wtResult of { Left msg -> attachFailure #matchWidgetType msg; Right () -> \x -> x })
+ |> (case catResult of { Left msg -> attachFailure #matchCategory msg; Right () -> \x -> x })
|> ifValid \case
Left r -> render EditView { rule = r, hubs }
Right r -> do
diff --git a/Web/Controller/LearningDashboard.hs b/Web/Controller/LearningDashboard.hs
index e6fa4f8..7a98f4f 100644
--- a/Web/Controller/LearningDashboard.hs
+++ b/Web/Controller/LearningDashboard.hs
@@ -8,8 +8,7 @@ import Web.View.LearningDashboard.Show
instance Controller LearningDashboardController where
beforeAction = ensureIsUser
- action LearningDashboardAction = do
- autoRefresh
+ action LearningDashboardAction = autoRefresh do
topCorrelations <- query @OutcomeCorrelation
|> orderByDesc #correlationScore
|> limit 10
diff --git a/Web/Controller/MarketplaceDashboard.hs b/Web/Controller/MarketplaceDashboard.hs
index 55141e8..a9f3e6b 100644
--- a/Web/Controller/MarketplaceDashboard.hs
+++ b/Web/Controller/MarketplaceDashboard.hs
@@ -5,8 +5,6 @@ import Web.View.MarketplaceDashboard.Show
import Generated.Types
import IHP.Prelude
import IHP.ControllerPrelude
-import Database.PostgreSQL.Simple (Query)
-
instance Controller MarketplaceDashboardController where
beforeAction = ensureIsUser
@@ -15,15 +13,32 @@ instance Controller MarketplaceDashboardController where
let mWType = paramOrNothing @Text "widgetType"
let sortBy = paramOrDefault @Text "adopted" "sort"
- -- Widget patterns: full-text search + filter
- patterns <- sqlQuery (patternQuery mSearch mWType sortBy) ()
+ -- Widget patterns: fetch then count adoptions
+ basePatterns <- query @WidgetPattern
+ |> filterWhere (#isPublished, True)
+ |> orderByAsc #name
+ |> limit 50
+ |> fetch
+ patterns <- mapM (\p -> do
+ cnt <- sqlQueryScalar
+ "SELECT COUNT(*) FROM pattern_adoptions WHERE widget_pattern_id = ?"
+ (Only p.id)
+ pure (p, fromMaybe 0 cnt)) basePatterns
- -- Governance templates: full-text search
- templates <- sqlQuery (templateQuery mSearch) ()
+ -- Governance templates: fetch then count clones
+ baseTemplates <- query @GovernanceTemplate
+ |> filterWhere (#isPublished, True)
+ |> limit 50
+ |> fetch
+ templates <- mapM (\t -> do
+ cnt <- sqlQueryScalar
+ "SELECT COUNT(*) FROM governance_template_clones WHERE governance_template_id = ?"
+ (Only t.id)
+ pure (t, fromMaybe 0 cnt)) baseTemplates
-- Trending patterns (most adoptions in last 30 days)
- trending <- sqlQuery
- "SELECT wp.id, wp.name, wp.widget_type, COUNT(pa.id) AS recent_adoptions \
+ trendingRaw :: [(Id WidgetPattern, Text, Text, Int)] <- sqlQuery
+ "SELECT wp.id, wp.name, wp.widget_type, CAST(COUNT(pa.id) AS integer) AS recent_adoptions \
\ FROM widget_patterns wp \
\ JOIN pattern_adoptions pa ON pa.widget_pattern_id = wp.id \
\ WHERE wp.is_published = TRUE \
@@ -32,6 +47,7 @@ instance Controller MarketplaceDashboardController where
\ ORDER BY recent_adoptions DESC \
\ LIMIT 5"
()
+ let trending = trendingRaw
widgetTypeOptions <- sqlQuery
"SELECT name, label FROM widget_type_registry WHERE status = 'active' ORDER BY label"
@@ -45,40 +61,3 @@ instance Controller MarketplaceDashboardController where
, sortOrder = sortBy
}
--- | Widget pattern list query with optional search and type filter.
-patternQuery :: Maybe Text -> Maybe Text -> Text -> Query
-patternQuery mSearch mWType sortBy =
- let baseWhere = "wp.is_published = TRUE"
- searchClause = case mSearch of
- Nothing -> ""
- Just _ -> " AND to_tsvector('english', wp.name || ' ' || COALESCE(wp.description,'')) \
- \ @@ plainto_tsquery(?)"
- typeClause = case mWType of
- Nothing -> ""
- Just _ -> " AND wp.widget_type = ?"
- orderClause = case sortBy of
- "recent" -> "wp.created_at DESC"
- "alpha" -> "wp.name ASC"
- _ -> "adopter_count DESC"
- in "SELECT wp.*, COUNT(pa.id) AS adopter_count \
- \ FROM widget_patterns wp \
- \ LEFT JOIN pattern_adoptions pa ON pa.widget_pattern_id = wp.id \
- \ WHERE " <> baseWhere <> searchClause <> typeClause <>
- " GROUP BY wp.id \
- \ ORDER BY " <> orderClause <>
- " LIMIT 50"
-
--- | Governance template list query with optional search.
-templateQuery :: Maybe Text -> Query
-templateQuery mSearch =
- let searchClause = case mSearch of
- Nothing -> ""
- Just _ -> " AND to_tsvector('english', gt.name || ' ' || COALESCE(gt.description,'')) \
- \ @@ plainto_tsquery(?)"
- in "SELECT gt.*, COUNT(gtc.id) AS clone_count \
- \ FROM governance_templates gt \
- \ LEFT JOIN governance_template_clones gtc ON gtc.governance_template_id = gt.id \
- \ WHERE gt.is_published = TRUE" <> searchClause <>
- " GROUP BY gt.id \
- \ ORDER BY clone_count DESC \
- \ LIMIT 50"
diff --git a/Web/View/AdaptiveThresholds/Index.hs b/Web/View/AdaptiveThresholds/Index.hs
index fa036dd..b6bf917 100644
--- a/Web/View/AdaptiveThresholds/Index.hs
+++ b/Web/View/AdaptiveThresholds/Index.hs
@@ -59,5 +59,5 @@ renderCfgStatus (Just cfg) = [hsx|
Last calibrated: {show cfg.calibrationDate}
- {maybe "" id cfg.notes}
+ {fromMaybe "" cfg.notes}
|]
diff --git a/Web/View/ApiConsumers/New.hs b/Web/View/ApiConsumers/New.hs
index f00fd60..0069df7 100644
--- a/Web/View/ApiConsumers/New.hs
+++ b/Web/View/ApiConsumers/New.hs
@@ -36,11 +36,11 @@ instance View NewView where
diff --git a/Web/View/DecisionRecords/Show.hs b/Web/View/DecisionRecords/Show.hs
index e3d1589..e8a8ee3 100644
--- a/Web/View/DecisionRecords/Show.hs
+++ b/Web/View/DecisionRecords/Show.hs
@@ -258,10 +258,10 @@ renderEvalSummary ev = [hsx|
|]
-starsFor :: Int16 -> Text
-starsFor n = pack (replicate (fromIntegral n) '★') <> pack (replicate (5 - fromIntegral n) '☆')
+starsFor :: Int -> Text
+starsFor n = cs (replicate n '★') <> cs (replicate (5 - n) '☆')
-scoreClass :: Int16 -> Text
+scoreClass :: Int -> Text
scoreClass n
| n <= 2 = "bg-red-100 text-red-800"
| n == 3 = "bg-yellow-100 text-yellow-800"
diff --git a/Web/View/DeploymentRecords/Index.hs b/Web/View/DeploymentRecords/Index.hs
index 012b090..90f82cc 100644
--- a/Web/View/DeploymentRecords/Index.hs
+++ b/Web/View/DeploymentRecords/Index.hs
@@ -69,24 +69,24 @@ renderRow decisions signals evaluations record = [hsx|
decisionTitle = maybe "(unknown)" (.title) $
find (\d -> d.id == record.decisionId) decisions
signalCount = length $ filter (\s -> s.deploymentId == record.id) signals
- mScore :: Maybe Int16
+ mScore :: Maybe Int
mScore = fmap (.score) $ find (\e -> e.deploymentId == record.id) evaluations
-renderMaybeScore :: Maybe Int16 -> Html
+renderMaybeScore :: Maybe Int -> Html
renderMaybeScore Nothing = [hsx|—|]
renderMaybeScore (Just score) = renderScoreBadge score
-renderScoreBadge :: Int16 -> Html
+renderScoreBadge :: Int -> Html
renderScoreBadge score = [hsx|
" text-xs px-2 py-0.5 rounded font-medium"}>
{starsFor score}
|]
-starsFor :: Int16 -> Text
-starsFor n = pack (replicate (fromIntegral n) '★') <> pack (replicate (5 - fromIntegral n) '☆')
+starsFor :: Int -> Text
+starsFor n = cs (replicate n '★') <> cs (replicate (5 - n) '☆')
-scoreClass :: Int16 -> Text
+scoreClass :: Int -> Text
scoreClass n
| n <= 2 = "bg-red-100 text-red-800"
| n == 3 = "bg-yellow-100 text-yellow-800"
diff --git a/Web/View/DeploymentRecords/Show.hs b/Web/View/DeploymentRecords/Show.hs
index 91abf30..fd0e6f0 100644
--- a/Web/View/DeploymentRecords/Show.hs
+++ b/Web/View/DeploymentRecords/Show.hs
@@ -6,6 +6,7 @@ import IHP.Prelude
import IHP.ViewPrelude
import Web.Routes ()
import Data.Int (Int16)
+import Data.Scientific (Scientific, toRealFloat)
data PeriodMetrics = PeriodMetrics
{ eventCount :: !Int
@@ -174,9 +175,9 @@ renderSignal sig = [hsx|
|]
-renderSignalValue :: Double -> Html
+renderSignalValue :: Scientific -> Html
renderSignalValue v = [hsx|
- {show v}
+ {show (toRealFloat v :: Double)}
|]
renderNoEvaluationForm :: Id DeploymentRecord -> Html
@@ -321,14 +322,14 @@ outcomeClass "merged" = "bg-indigo-100 text-indigo-800"
outcomeClass "reframed" = "bg-orange-100 text-orange-800"
outcomeClass _ = "bg-gray-100 text-gray-600"
-scoreClass :: Int16 -> Text
+scoreClass :: Int -> Text
scoreClass n
| n <= 2 = "bg-red-100 text-red-800"
| n == 3 = "bg-yellow-100 text-yellow-800"
| otherwise = "bg-green-100 text-green-800"
-starsFor :: Int16 -> Text
-starsFor n = pack (replicate (fromIntegral n) '★') <> pack (replicate (5 - fromIntegral n) '☆')
+starsFor :: Int -> Text
+starsFor n = cs (replicate n '★') <> cs (replicate (5 - n) '☆')
userName :: [User] -> Maybe (Id User) -> Text
userName _ Nothing = "—"
diff --git a/Web/View/GovernanceTemplates/New.hs b/Web/View/GovernanceTemplates/New.hs
index d86a05a..36fa2a7 100644
--- a/Web/View/GovernanceTemplates/New.hs
+++ b/Web/View/GovernanceTemplates/New.hs
@@ -54,7 +54,7 @@ instance View NewView where
+ placeholder="{"steps": [], "questions": []}">