generated from coulomb/repo-seed
fix(WP-0014/A2): continued type-correctness fixes and Tailwind CSS output
- Schema.sql: add FK constraints for phases 6–12 so IHP generates Id X instead of UUID for FK columns (widget_adapter_specs, friction_scores, hub_routing_rules, agent_proposals, hub_capability_manifests, etc.) - HubHealth, ModelRouter, ApiInteractionEvents: remove toUUID() wrappers now that FK columns carry proper Id types - FederatedGovernance/Dashboard, HubRoutingRules/Index: same Id comparison fix - AgentProposals/Index, DecisionRecords/Index, ApiConsumers/Edit: Id type fixes - BottleneckDetector: add Data.Coerce import; CrossHubPropagation: add guard - ApiKeys: qualify cryptohash-sha256 import to resolve package ambiguity - WebhookDeliveryJob: use LBS.fromStrict; remove duplicate diffUTCTime - Sessions/New: use renderFlashMessages (IHP built-in) - ArchiveRecords/LineageInspector: simplify renderChainStep signature - static/app.css: Tailwind CSS output (2011 lines) — A3 confirmed - workplans/IHUB-WP-0015-local-deployment-intro-ui.md: add workplan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import Generated.Types
|
||||
import Web.Routes ()
|
||||
import Data.Time.Clock (addUTCTime, getCurrentTime, NominalDiffTime)
|
||||
import Database.PostgreSQL.Simple (Only(..))
|
||||
import Data.Coerce (coerce)
|
||||
|
||||
-- | Severity based on how much older than the threshold the record is.
|
||||
staleSeverity :: NominalDiffTime -> NominalDiffTime -> Text
|
||||
|
||||
@@ -9,6 +9,7 @@ import Web.Routes ()
|
||||
import Data.Time.Clock (addUTCTime, getCurrentTime)
|
||||
import Data.Aeson (toJSON)
|
||||
import qualified Data.List as List
|
||||
import Control.Monad (guard)
|
||||
|
||||
-- | Detect cross-hub propagation patterns and insert CrossHubPropagation rows.
|
||||
-- Idempotent: skips patterns for which an open/acknowledged record already exists.
|
||||
|
||||
@@ -51,7 +51,7 @@ computeHubHealth hubId widgets candidates decisions deployments signals annotati
|
||||
score = max 0 (100 - deductions)
|
||||
|
||||
newRecord @HubHealthSnapshot
|
||||
|> set #hubId (toUUID hubId)
|
||||
|> set #hubId hubId
|
||||
|> set #healthScore score
|
||||
|> set #openCandidates openCount
|
||||
|> set #regressedWidgets regCount
|
||||
|
||||
@@ -24,7 +24,7 @@ resolveAgent hubId taskType = do
|
||||
\ LIMIT 1"
|
||||
(hubId, taskType)
|
||||
case rows of
|
||||
[Only agentId] -> fetchOneOrNothing agentId
|
||||
[Only (agentId :: Id AgentRegistration)] -> fetchOneOrNothing agentId
|
||||
_ -> pure Nothing
|
||||
|
||||
-- | Return all active AgentRegistrations for a hub + task_type ordered by
|
||||
@@ -39,4 +39,4 @@ resolveAllAgents hubId taskType = do
|
||||
\ WHERE mrp.hub_id = ? AND mrp.task_type = ? AND mrp.is_active = TRUE \
|
||||
\ ORDER BY mrp.priority DESC"
|
||||
(hubId, taskType)
|
||||
mapM (fetch . (\(Only i) -> i)) rows
|
||||
mapM (\(Only (i :: Id AgentRegistration)) -> fetch i) rows
|
||||
|
||||
@@ -1053,3 +1053,93 @@ ALTER TABLE agent_review_records ADD FOREIGN KEY (proposal_id) REFERENCES agent_
|
||||
ALTER TABLE confidence_annotations ADD FOREIGN KEY (proposal_id) REFERENCES agent_proposals(id);
|
||||
ALTER TABLE institutional_knowledge_entries ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE institutional_knowledge_entries ADD FOREIGN KEY (decision_record_id) REFERENCES decision_records(id);
|
||||
|
||||
-- Additional FK constraints for IHP type generation (Id vs UUID)
|
||||
-- Phase 6 — widget_adapter_specs
|
||||
ALTER TABLE widget_adapter_specs ADD FOREIGN KEY (envelope_contract_id) REFERENCES envelope_emission_contracts(id);
|
||||
ALTER TABLE widget_adapter_specs ADD FOREIGN KEY (reporting_contract_id) REFERENCES interaction_reporting_contracts(id);
|
||||
ALTER TABLE widgets ADD FOREIGN KEY (adapter_spec_id) REFERENCES widget_adapter_specs(id);
|
||||
|
||||
-- Phase 7 — friction_scores, bottleneck_records, hub_health_snapshots
|
||||
ALTER TABLE friction_scores ADD FOREIGN KEY (widget_id) REFERENCES widgets(id);
|
||||
ALTER TABLE bottleneck_records ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE hub_health_snapshots ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE cross_hub_propagations ADD FOREIGN KEY (source_hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- Phase 8 — widget_ownerships, hub_routing_rules, stewardship_roles
|
||||
ALTER TABLE widget_ownerships ADD FOREIGN KEY (widget_id) REFERENCES widgets(id);
|
||||
ALTER TABLE widget_ownerships ADD FOREIGN KEY (owner_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE widget_ownerships ADD FOREIGN KEY (steward_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE hub_routing_rules ADD FOREIGN KEY (source_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE hub_routing_rules ADD FOREIGN KEY (target_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE stewardship_roles ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE requirement_candidates ADD FOREIGN KEY (routed_to_hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- User actor references (nullable)
|
||||
ALTER TABLE annotation_threads ADD FOREIGN KEY (created_by) REFERENCES users(id);
|
||||
ALTER TABLE requirement_candidates ADD FOREIGN KEY (created_by) REFERENCES users(id);
|
||||
ALTER TABLE triage_states ADD FOREIGN KEY (changed_by) REFERENCES users(id);
|
||||
ALTER TABLE requirements ADD FOREIGN KEY (created_by) REFERENCES users(id);
|
||||
ALTER TABLE decision_records ADD FOREIGN KEY (decided_by) REFERENCES users(id);
|
||||
ALTER TABLE policy_references ADD FOREIGN KEY (created_by) REFERENCES users(id);
|
||||
ALTER TABLE implementation_change_references ADD FOREIGN KEY (linked_by) REFERENCES users(id);
|
||||
ALTER TABLE deployment_records ADD FOREIGN KEY (deployed_by) REFERENCES users(id);
|
||||
ALTER TABLE agent_review_records ADD FOREIGN KEY (reviewer_id) REFERENCES users(id);
|
||||
|
||||
-- change_evaluations
|
||||
ALTER TABLE change_evaluations ADD FOREIGN KEY (deployment_id) REFERENCES deployment_records(id);
|
||||
ALTER TABLE change_evaluations ADD FOREIGN KEY (decision_id) REFERENCES decision_records(id);
|
||||
ALTER TABLE change_evaluations ADD FOREIGN KEY (evaluated_by) REFERENCES users(id);
|
||||
|
||||
-- agent_proposals source refs
|
||||
ALTER TABLE agent_proposals ADD FOREIGN KEY (source_widget_id) REFERENCES widgets(id);
|
||||
ALTER TABLE agent_proposals ADD FOREIGN KEY (source_candidate_id) REFERENCES requirement_candidates(id);
|
||||
ALTER TABLE agent_proposals ADD FOREIGN KEY (source_thread_id) REFERENCES annotation_threads(id);
|
||||
ALTER TABLE agent_proposals ADD FOREIGN KEY (source_decision_id) REFERENCES decision_records(id);
|
||||
ALTER TABLE agent_proposals ADD FOREIGN KEY (agent_registration_id) REFERENCES agent_registrations(id);
|
||||
|
||||
-- GAAF type registry owner refs (nullable)
|
||||
ALTER TABLE widget_type_registry ADD FOREIGN KEY (owner_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE event_type_registry ADD FOREIGN KEY (owner_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE annotation_category_registry ADD FOREIGN KEY (owner_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE policy_scope_registry ADD FOREIGN KEY (owner_hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- hub_capability_manifests
|
||||
ALTER TABLE hub_capability_manifests ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- Phase 9 — api_consumers, webhook_deliveries, api_request_log
|
||||
ALTER TABLE api_consumers ADD FOREIGN KEY (hub_capability_manifest_id) REFERENCES hub_capability_manifests(id);
|
||||
ALTER TABLE webhook_deliveries ADD FOREIGN KEY (webhook_subscription_id) REFERENCES webhook_subscriptions(id);
|
||||
ALTER TABLE api_request_log ADD FOREIGN KEY (api_consumer_id) REFERENCES api_consumers(id);
|
||||
|
||||
-- Phase 10 — widget_patterns, pattern_adoptions, governance_templates
|
||||
ALTER TABLE widget_patterns ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE widget_pattern_versions ADD FOREIGN KEY (widget_pattern_id) REFERENCES widget_patterns(id);
|
||||
ALTER TABLE pattern_adoptions ADD FOREIGN KEY (adopting_hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE pattern_adoptions ADD FOREIGN KEY (pinned_version_id) REFERENCES widget_pattern_versions(id);
|
||||
ALTER TABLE governance_templates ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE governance_template_clones ADD FOREIGN KEY (governance_template_id) REFERENCES governance_templates(id);
|
||||
ALTER TABLE governance_template_clones ADD FOREIGN KEY (cloning_hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- Phase 11 — agent_registrations, model_routing_policies, agent_delegations, collective_proposals
|
||||
ALTER TABLE agent_registrations ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE model_routing_policies ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE model_routing_policies ADD FOREIGN KEY (agent_registration_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE agent_delegations ADD FOREIGN KEY (delegating_agent_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE agent_delegations ADD FOREIGN KEY (receiving_agent_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE agent_delegations ADD FOREIGN KEY (parent_proposal_id) REFERENCES agent_proposals(id);
|
||||
ALTER TABLE collective_proposals ADD FOREIGN KEY (source_widget_id) REFERENCES widgets(id);
|
||||
ALTER TABLE collective_proposals ADD FOREIGN KEY (source_candidate_id) REFERENCES requirement_candidates(id);
|
||||
ALTER TABLE collective_proposal_contributions ADD FOREIGN KEY (collective_proposal_id) REFERENCES collective_proposals(id);
|
||||
ALTER TABLE collective_proposal_contributions ADD FOREIGN KEY (agent_registration_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE ai_governance_policies ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE ai_governance_policies ADD FOREIGN KEY (agent_registration_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE agent_performance_records ADD FOREIGN KEY (agent_registration_id) REFERENCES agent_registrations(id);
|
||||
ALTER TABLE agent_performance_records ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
|
||||
-- Phase 12 — outcome_correlations, pattern_performance_records, adaptive_threshold_configs, learning_insights
|
||||
ALTER TABLE outcome_correlations ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE pattern_performance_records ADD FOREIGN KEY (widget_pattern_id) REFERENCES widget_patterns(id);
|
||||
ALTER TABLE pattern_performance_records ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE adaptive_threshold_configs ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
ALTER TABLE learning_insights ADD FOREIGN KEY (hub_id) REFERENCES hubs(id);
|
||||
|
||||
@@ -80,7 +80,7 @@ createEventForHub hub = do
|
||||
Nothing -> do
|
||||
renderJsonWithStatusCode status422 (object ["error" .= ("Widget not found" :: Text)])
|
||||
Just widget -> do
|
||||
when (widget.hubId /= toUUID hub.id) do
|
||||
when (widget.hubId /= hub.id) do
|
||||
renderJsonWithStatusCode status403 (object ["error" .= ("Widget does not belong to this hub" :: Text)])
|
||||
|
||||
event <- newRecord @InteractionEvent
|
||||
|
||||
@@ -7,7 +7,7 @@ import Generated.Types
|
||||
import IHP.Prelude
|
||||
import IHP.ControllerPrelude
|
||||
import qualified Data.Text.Encoding as TE
|
||||
import qualified Crypto.Hash.SHA256 as SHA256
|
||||
import qualified "cryptohash-sha256" Crypto.Hash.SHA256 as SHA256
|
||||
import qualified Data.ByteString.Base16 as Base16
|
||||
import qualified Data.ByteString.Random as Random
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ attempt sub payload attemptNo = do
|
||||
$ HTTP.setRequestHeader "Content-Type" ["application/json"]
|
||||
$ HTTP.setRequestHeader "X-IHF-Signature" [TE.encodeUtf8 sig]
|
||||
$ HTTP.setRequestHeader "X-IHF-Event" [TE.encodeUtf8 sub.eventType]
|
||||
$ HTTP.setRequestBodyBS payloadBytes req
|
||||
$ HTTP.setRequestBodyLBS (LBS.fromStrict payloadBytes) req
|
||||
HTTP.httpLBS req'
|
||||
endTime <- getCurrentTime
|
||||
let latencyMs = round (realToFrac (diffUTCTime endTime startTime) * 1000 :: Double) :: Int
|
||||
@@ -104,5 +104,3 @@ hmacSha256Hex secret payload =
|
||||
digest = SHA256.hash combined
|
||||
in TE.decodeUtf8 (Base16.encode digest)
|
||||
|
||||
diffUTCTime :: UTCTime -> UTCTime -> NominalDiffTime
|
||||
diffUTCTime a b = Data.Time.diffUTCTime a b
|
||||
|
||||
@@ -97,7 +97,7 @@ renderRow widgets p = [hsx|
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600">{widgetName widgets p.sourceWidgetId}</td>
|
||||
<td class="px-4 py-3">{renderConfidenceBar p.confidence}</td>
|
||||
<td class="px-4 py-3">{renderConfidenceBar (fmap realToFrac p.confidence)}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class={statusBadge p.status <> " text-xs px-2 py-0.5 rounded font-medium"}>
|
||||
{p.status}
|
||||
@@ -107,9 +107,9 @@ renderRow widgets p = [hsx|
|
||||
</tr>
|
||||
|]
|
||||
|
||||
widgetName :: [Widget] -> Maybe UUID -> Text
|
||||
widgetName :: [Widget] -> Maybe (Id Widget) -> Text
|
||||
widgetName _ Nothing = "—"
|
||||
widgetName widgets (Just wid) = maybe "(unknown)" (.name) (find (\w -> toUUID w.id == wid) widgets)
|
||||
widgetName widgets (Just wid) = maybe "(unknown)" (.name) (find (\w -> w.id == wid) widgets)
|
||||
|
||||
renderConfidenceBar :: Maybe Double -> Html
|
||||
renderConfidenceBar Nothing = [hsx|<span class="text-gray-300 text-xs">—</span>|]
|
||||
|
||||
@@ -54,7 +54,7 @@ instance View EditView where
|
||||
|]
|
||||
where
|
||||
manifestOption selectedId m = [hsx|
|
||||
<option value={show m.id} selected={selectedId == Just (toUUID m.id)}>
|
||||
<option value={show m.id} selected={selectedId == Just m.id}>
|
||||
Manifest {show m.id} ({m.status})
|
||||
</option>
|
||||
|]
|
||||
|
||||
@@ -31,14 +31,14 @@ instance View LineageInspectorView where
|
||||
<p class="text-sm text-gray-500 mb-6">Full traceability chain for this widget.</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
{renderChainStep "1" "Widget" 1 (Just $ ShowWidgetAction { widgetId = widget.id })}
|
||||
{renderChainStep "2" "Interaction Events" (length events) Nothing}
|
||||
{renderChainStep "3" "Annotations" (length annotations) Nothing}
|
||||
{renderChainStep "4" "Requirement Candidates" (length candidates) Nothing}
|
||||
{renderChainStep "5" "Requirements" (length requirements) Nothing}
|
||||
{renderChainStep "6" "Decision Records" (length decisions) Nothing}
|
||||
{renderChainStep "7" "Deployments" (length deployments) Nothing}
|
||||
{renderChainStep "8" "Outcome Signals" (length signals) Nothing}
|
||||
{renderChainStep "1" "Widget" 1}
|
||||
{renderChainStep "2" "Interaction Events" (length events)}
|
||||
{renderChainStep "3" "Annotations" (length annotations)}
|
||||
{renderChainStep "4" "Requirement Candidates" (length candidates)}
|
||||
{renderChainStep "5" "Requirements" (length requirements)}
|
||||
{renderChainStep "6" "Decision Records" (length decisions)}
|
||||
{renderChainStep "7" "Deployments" (length deployments)}
|
||||
{renderChainStep "8" "Outcome Signals" (length signals)}
|
||||
</div>
|
||||
|
||||
{maybe mempty renderArchivePanel mArchive}
|
||||
@@ -68,8 +68,8 @@ instance View LineageInspectorView where
|
||||
</div>
|
||||
|]
|
||||
|
||||
renderChainStep :: Text -> Text -> Int -> Maybe a -> Html
|
||||
renderChainStep stepNum label count mLink = [hsx|
|
||||
renderChainStep :: Text -> Text -> Int -> Html
|
||||
renderChainStep stepNum label count = [hsx|
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 flex items-center justify-center text-sm font-medium flex-shrink-0">
|
||||
{stepNum}
|
||||
|
||||
@@ -94,9 +94,9 @@ linkedReqTitle :: [Requirement] -> Maybe (Id Requirement) -> Text
|
||||
linkedReqTitle _ Nothing = "—"
|
||||
linkedReqTitle reqs (Just rid) = maybe "(unknown)" (.title) (find (\r -> r.id == rid) reqs)
|
||||
|
||||
userName :: [User] -> Maybe UUID -> Text
|
||||
userName :: [User] -> Maybe (Id User) -> Text
|
||||
userName _ Nothing = "—"
|
||||
userName users (Just uid) = maybe "(unknown)" (.name) (find (\u -> toUUID u.id == uid) users)
|
||||
userName users (Just uid) = maybe "(unknown)" (.name) (find (\u -> u.id == uid) users)
|
||||
|
||||
outcomeClass :: Text -> Text
|
||||
outcomeClass "accepted" = "bg-green-100 text-green-800"
|
||||
|
||||
@@ -81,7 +81,7 @@ instance View FederatedGovernanceDashboardView where
|
||||
-- ── Panel 2: Routing activity ─────────────────────────────────────
|
||||
activeRulesCount = length rules
|
||||
routedCount = length routedCandidates
|
||||
hubName hid = maybe (show hid) (.name) (find (\h -> toUUID h.id == hid) hubs)
|
||||
hubName hid = maybe (show hid) (.name) (find (\h -> h.id == hid) hubs)
|
||||
|
||||
panel2Routing = [hsx|
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-5">
|
||||
@@ -159,7 +159,7 @@ instance View FederatedGovernanceDashboardView where
|
||||
hubsWithStewards = List.nub (map (.hubId) stewards)
|
||||
stewardedCount = length hubsWithStewards
|
||||
totalHubs = length hubs
|
||||
hubsWithNoSteward = filter (\h -> toUUID h.id `notElem` hubsWithStewards) hubs
|
||||
hubsWithNoSteward = filter (\h -> h.id `notElem` hubsWithStewards) hubs
|
||||
|
||||
panel4Stewardship = [hsx|
|
||||
<div class="bg-white rounded-lg border border-gray-200 p-5">
|
||||
|
||||
@@ -47,7 +47,7 @@ renderRulesList rules hubs = [hsx|
|
||||
|
||||
renderRoutingRuleRow :: [Hub] -> HubRoutingRule -> Html
|
||||
renderRoutingRuleRow hubs r =
|
||||
let hubName hid = maybe (show hid) (.name) (find (\h -> toUUID h.id == hid) hubs)
|
||||
let hubName hid = maybe (show hid) (.name) (find (\h -> h.id == hid) hubs)
|
||||
in [hsx|
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 font-medium text-gray-800">
|
||||
|
||||
@@ -13,7 +13,7 @@ instance View NewView where
|
||||
<div class="max-w-sm mx-auto mt-16">
|
||||
<h1 class="text-2xl font-semibold mb-6">Sign in to inter-hub</h1>
|
||||
<form method="POST" action={CreateSessionAction} class="space-y-4">
|
||||
{forEach (getFlashMessages) renderFlash}
|
||||
{renderFlashMessages}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<input type="email" name="email" required
|
||||
@@ -35,7 +35,3 @@ instance View NewView where
|
||||
</div>
|
||||
|]
|
||||
|
||||
renderFlash :: Text -> Html
|
||||
renderFlash msg = [hsx|
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 rounded px-3 py-2 text-sm">{msg}</div>
|
||||
|]
|
||||
|
||||
2011
static/app.css
2011
static/app.css
File diff suppressed because it is too large
Load Diff
89
workplans/IHUB-WP-0015-local-deployment-intro-ui.md
Normal file
89
workplans/IHUB-WP-0015-local-deployment-intro-ui.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
id: IHUB-WP-0015
|
||||
type: workplan
|
||||
title: "Local Deployment — Intro and Tutorial Web UI"
|
||||
domain: inter_hub
|
||||
repo: inter-hub
|
||||
status: open
|
||||
owner: custodian
|
||||
topic_slug: inter_hub
|
||||
created: "2026-04-03"
|
||||
updated: "2026-04-03"
|
||||
state_hub_sync: done
|
||||
state_hub_workstream_id: "946d50b8-441c-4c0a-b1a0-2a4fb3340d16"
|
||||
depends_on: IHUB-WP-0014
|
||||
---
|
||||
|
||||
# IHUB-WP-0015 — Local Deployment: Intro and Tutorial Web UI
|
||||
|
||||
## Goal
|
||||
|
||||
Deploy inter-hub locally and add the public-facing intro/tutorial web
|
||||
interface so that the running instance presents both a self-contained
|
||||
introduction to the framework and access to the management UI — with no
|
||||
domain extensions registered yet.
|
||||
|
||||
## Background
|
||||
|
||||
Depends on IHUB-WP-0014 (clean build, Tailwind CSS, admin user). Once the
|
||||
pre-flight gaps are closed, this workplan adds the missing web layer: a
|
||||
landing page, capabilities overview, and domain-extension tutorial as actual
|
||||
rendered pages within the IHP app — surfacing the rich content already in
|
||||
`docs/` and `specs/` through the web interface rather than only as files.
|
||||
|
||||
The management UI (58 controllers, all dashboards) already exists and needs
|
||||
no new code — only a navigational entry point.
|
||||
|
||||
## Tasks
|
||||
|
||||
### B1 — Create `StaticPages` controller
|
||||
New `Web/Controller/StaticPages.hs` with actions:
|
||||
`LandingAction`, `CapabilitiesAction`, `TutorialAction`, `ExtensionGuideAction`.
|
||||
No auth guard — these pages are public. Register routes in `Web/Routes.hs`.
|
||||
|
||||
### B2 — Landing page view
|
||||
`Web/View/StaticPages/Landing.hs` — what inter-hub is, the traceability chain
|
||||
(Widget → InteractionEvent/Annotation → RequirementCandidate → Requirement →
|
||||
DecisionRecord → DeploymentRecord → OutcomeSignal → Learning), key capabilities
|
||||
at a glance, and two CTAs: "Explore Capabilities" + "Go to Management UI".
|
||||
Content drawn from `SCOPE.md` and `ARCHITECTURE-LAYERS.md`.
|
||||
|
||||
### B3 — Capabilities page view
|
||||
`Web/View/StaticPages/Capabilities.hs` — the 12-phase capability map, GAAF
|
||||
scorecard (3.68 Strong), the API surface (v1 + v2), the learning loop, the
|
||||
type registry system, and the hub federation model. Structured HTML with
|
||||
section anchors. Draws from `ARCHITECTURE-LAYERS.md` and
|
||||
`specs/InteractionHubFrameworkSpecification_v0.2.md`.
|
||||
|
||||
### B4 — Tutorial and extension guide views
|
||||
`Web/View/StaticPages/Tutorial.hs` — step-by-step overview of how the IHF
|
||||
works from a developer perspective (Widget lifecycle, governance flow, outcome
|
||||
loop).
|
||||
`Web/View/StaticPages/ExtensionGuide.hs` — how to build and register a domain
|
||||
hub extension: HubCapabilityManifest, type registry entries, hub-owned widget
|
||||
types, hub-scoped controllers. Content drawn from
|
||||
`docs/domain-hub-extension-guide.md`.
|
||||
|
||||
### B5 — Update root route
|
||||
Change `GET /` from `HubsAction` to `LandingAction` in `Web/FrontController.hs`
|
||||
or `Web/Routes.hs`. Management entry point becomes `GET /hubs`.
|
||||
|
||||
### B6 — Navigation integration
|
||||
Add "Docs" / "About" / "Tutorial" links to the existing nav bar in
|
||||
`Web/FrontController.hs`. Add "Management" link pointing to `/hubs`.
|
||||
Ensure the nav is accessible from all static pages without requiring login.
|
||||
|
||||
### B7 — Final deployment run and verification
|
||||
With WP-0014 complete: confirm `devenv up` is clean, log in as admin, walk
|
||||
the full intro → capabilities → tutorial flow, enter the management UI,
|
||||
create a test hub, verify all dashboards load. Document the local URL, admin
|
||||
credentials location, and any operational notes.
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- `http://localhost:8000` shows the landing page (no login required)
|
||||
- Landing → Capabilities → Tutorial → ExtensionGuide navigation works
|
||||
- "Management UI" link from landing takes authenticated users to `/hubs`
|
||||
- Hub management, all dashboards, and API v2 remain functional
|
||||
- No domain extensions pre-registered (clean starting state confirmed)
|
||||
- All pages render with correct Tailwind styling
|
||||
Reference in New Issue
Block a user