Files
inter-hub/docs/research/personal-dashboard-current-state.md
tegwick d6b655a5cf docs: complete personal dashboard framework and implementation plan
Finish IHUB-WP-0020 design work (status finished, all design tasks done)
and add IHUB-WP-0021 with the 12-task implementation workplan plus
research, PRs, and FDD deliverables produced during the 2026-06-16 review.
2026-06-21 16:11:37 +02:00

14 KiB

Personal Dashboard Current-State Research

Workplan: IHUB-WP-0020 Date: 2026-06-16 Status: Research deliverable for T01

Purpose

This note reviews the current inter-hub implementation before designing a personal dashboard framework. The main finding is that inter-hub already has a large set of server-rendered dashboard surfaces, governed widget identity, type registries, annotations, event capture, hub health, API usage, marketplace, and learning data. The personal dashboard should compose these capabilities instead of inventing a separate dashboard product.

External dashboard products are used here only for pattern extraction. The implementation direction remains IHP, HSX, Tailwind, server-rendered forms, and existing IHF governance primitives.

Evidence Reviewed

Repo files inspected for this note:

  • Web/Controller/Hubs.hs
  • Web/Controller/Sessions.hs
  • Web/FrontController.hs
  • Web/Routes.hs
  • Web/Types.hs
  • Application/Schema.sql
  • Application/Helper/View.hs
  • Web/Controller/FederatedGovernance.hs
  • Web/Controller/FederatedPolicyOverlays.hs
  • Web/Controller/ApiDashboard.hs
  • Web/Controller/MarketplaceDashboard.hs
  • Web/Controller/LearningDashboard.hs
  • docs/phase1-summary.md through docs/phase8-summary.md
  • docs/ihp-ihf-mapping.md
  • docs/widget-envelope-convention.md
  • Workplans IHUB-WP-0001 through IHUB-WP-0015 where dashboard scope was introduced.

Current Authenticated Entry Point

The public root and documentation pages already exist through IHUB-WP-0015 and are registered last in Web.FrontController. The authenticated login flow still redirects to HubsAction:

login user
redirectTo HubsAction

HubsAction renders a table of hubs. It is useful as an admin list, but it is not a personal daily operating surface.

The sidebar already links to several platform surfaces, including Hubs, Learning, Ops Review, Federation, API Dashboard, Hub Registry, and Marketplace. The personal dashboard should therefore become a new authenticated landing route and a sidebar entry, while public root behavior remains unchanged.

Existing Dashboard Inventory

Surface Action Scope Live? Reuse potential
Hub list HubsAction Global hub table No Source for watched hubs and hub selector
Hub show ShowHubAction One hub Yes Recent events, annotations, widgets, manifest summary
Triage dashboard TriageDashboardAction One hub Yes Open candidates, recent escalations, annotation breakdown
Governance dashboard GovernanceDashboardAction One hub Yes Accepted candidates, requirements, decisions, traceability
Antifragility dashboard AntifragilityDashboardAction One hub Yes Deployments, outcome signals, recurrence leaderboard
Agent audit dashboard AgentAuditDashboardAction One hub context, global data Yes Agent proposals and review status
Adapter compatibility AdapterCompatibilityDashboardAction One hub Yes Adapter and contract compatibility panels
Friction heatmap FrictionHeatmapAction One hub Yes Widget friction summary
Bottleneck dashboard BottleneckDashboardAction One hub Yes Open bottlenecks
Hub health history HubHealthHistoryAction One hub Yes Health snapshot trend
Operational review board OperationalReviewBoardAction Global Yes Hub health, top friction, bottlenecks, propagations
Federated governance FederatedGovernanceDashboardAction Global Yes Ownership, routing, policy, stewardship, archive activity
Policy compliance PolicyComplianceDashboardAction Global Yes Active overlays and policy reference coverage
API dashboard ShowApiDashboardAction Global API consumers Yes Per-consumer request volume, error rate, last seen
Marketplace MarketplaceDashboardAction Global patterns/templates Yes Trending patterns, search/filter catalogue
Learning dashboard LearningDashboardAction Global learning memory Yes Insights, knowledge highlights, pattern rankings

The reusable unit today is not a standalone panel renderer. It is a controller query plus an HSX view fragment. WP-0021 should extract only the first small set of renderers needed for the personal dashboard. A broad refactor of existing dashboards is explicitly unnecessary for the first slice.

AutoRefresh and Query Patterns

Most dashboard actions wrap the whole action with autoRefresh do. This is simple and consistent with the existing IHP style. The current app does not have a reusable per-panel refresh abstraction.

Useful bounded patterns already exist:

  • ShowHubAction limits recent interaction events to 50 and annotations to 20.
  • GovernanceDashboardAction limits recent decisions to 20.
  • LearningDashboardAction limits correlations, rankings, insights, and knowledge highlights.
  • MarketplaceDashboardAction limits published patterns/templates and casts the trending adoption count to integer.

Risky or broad patterns to avoid copying directly:

  • Some hub dashboards fetch all related records for a hub and filter in memory. That is acceptable for small scoped screens, but a personal dashboard should bound every panel query by hub, time, status, and limit.
  • Several global dashboards fetch all hubs or all decision records. A personal view should either limit these or explicitly display only summarized rows.
  • Raw COUNT(*) queries should cast to integer or decode as Int64. Recent production work exposed PostgreSQL/Haskell decode failures when COUNT(*) was decoded as Int.

Recommendation: first implementation can wrap the whole personal dashboard action in autoRefresh do. The FDD should leave finer-grained panel refresh as a later optimization unless a simple route-level fragment pattern emerges.

Governed Widget and Annotation Constraints

Application.Helper.View.widgetEnvelope wraps a Widget record and injects governance metadata:

  • data-widget-id
  • data-widget-type
  • data-hub-id
  • data-capability-ref
  • data-view-context
  • data-policy-scope
  • data-widget-version

It also renders an Annotate link to the widget annotation view. The helper warns when view_context is absent. Therefore, a saved personal dashboard panel must not be treated as transient markup. It needs stable widget identity.

The registry seed includes a framework-level widget type named panel. That is the best first choice for saved dashboard panels. A saved panel instance should create or reference a widgets row with:

  • widget_type = 'panel'
  • capability_ref = 'personal-dashboard.<panel-key>'
  • view_context = 'personal-dashboard/<panel-key>'
  • policy_scope = 'internal'
  • status = 'active'

The FDD should decide the owning hub rule. Recommended first slice: use the framework hub for personal dashboard panel widgets and store source hub filters in panel config. This keeps the personal dashboard itself governed by inter-hub while still letting panels point at hub-specific data.

Schema Available for First-Slice Panels

Existing tables with direct panel value:

  • hubs
  • widgets
  • widget_versions
  • interaction_events
  • annotations
  • annotation_threads
  • requirement_candidates
  • triage_states
  • reviewer_assignments
  • requirements
  • decision_records
  • deployment_records
  • outcome_signals
  • friction_scores
  • bottleneck_records
  • hub_health_snapshots
  • cross_hub_propagations
  • widget_ownerships
  • hub_routing_rules
  • federated_policy_overlays
  • stewardship_roles
  • archive_records
  • widget_type_registry
  • event_type_registry
  • annotation_category_registry
  • policy_scope_registry
  • hub_capability_manifests
  • api_consumers
  • api_request_log
  • widget_patterns
  • pattern_adoptions
  • governance_templates
  • governance_template_clones
  • outcome_correlations
  • pattern_performance_records
  • adaptive_threshold_configs
  • institutional_knowledge_entries
  • learning_insights

Important gaps:

  • No personal_dashboards table.
  • No dashboard_panel_types table.
  • No dashboard_panels table.
  • No saved watched-hub set or user preference table.
  • No user role column.
  • No panel config decoder/validator.
  • No dedicated panel renderer module.
  • No explicit default dashboard seeding helper.

User and Role Model Findings

The users table has email, password hash, name, lockout fields, and created time. It has no role. stewardship_roles stores assigned_to as text and is hub-scoped. That can help infer operator relevance, but it is not a reliable role foreign key.

Recommendation: do not add users.role for the first slice. Seed a neutral default dashboard for all authenticated users, then allow the user to edit panel layout and filters. If a default needs hub relevance, match active stewardship_roles.assigned_to against user email or name as a best-effort hint, not as an authorization rule.

First-Slice Panel Candidates

The following panels are practical without broad refactoring:

Panel key Source Default filter Notes
watched-hubs hubs, latest hub_health_snapshots all hubs, limit 12 First panel can be neutral until watched hubs exist
recent-interactions interaction_events, widgets, hubs last 24h, limit 25 Existing indexes support recent ordering
triage-queue requirement_candidates status = 'open', limit 10 Can join source widget/hub for context
recent-decisions decision_records last 30 days, limit 10 Good governance reviewer entry point
hub-health hub_health_snapshots, bottleneck_records latest per hub, limit 12 Needs bounded latest-per-hub query
learning-digest learning_insights, institutional_knowledge_entries latest, limit 5/5 Already bounded in existing dashboard

Panels to defer until after the framework is proven:

  • agent-proposals
  • api-usage
  • marketplace-trending
  • my-annotations
  • adapter-compatibility
  • policy-compliance

The deferred panels are valuable, but they are not needed to prove dashboard persistence, layout, panel renderer dispatch, and governed panel identity.

External Pattern Extraction

System Useful pattern Translation for inter-hub
Grafana Dashboard as saved grid of panels with variables and refresh behavior Save panel rows plus hub/time filters; keep server-rendered refresh
Kibana dashboards Saved searches and time range awareness Treat panel query config as explicit, bounded, validated config
Retool/Appsmith Widget catalogue and data binding Use a server-side panel catalogue; avoid client runtime/data binding
Linear home Personal "my work" aggregation across entities Make the personal dashboard a daily work queue, not a clone of every dashboard
Notion linked databases Multiple saved views over the same records Let panels define filter/sort/display options against existing tables
Metabase Question as a governed reusable unit Treat panel renderer plus validated config as the reusable unit
Streamlit Simple declarative layout vocabulary Use predictable grid rows/spans and forms rather than drag-and-drop

The key pattern across these systems is not visual complexity. It is that a dashboard is a saved composition of bounded questions/panels with explicit parameters. For inter-hub, those questions must remain governed IHF widgets.

Answers to WP-0020 Research Questions

Which existing fragments can become first-slice renderers?

Good first-slice candidates:

  • Recent activity from ShowHubAction.
  • Open candidate queue from TriageDashboardAction.
  • Recent decisions from GovernanceDashboardAction.
  • Latest hub health from OperationalReviewBoardAction and HubHealthHistoryAction.
  • Learning digest from LearningDashboardAction.
  • Watched hubs from HubsAction plus latest health snapshots.

These can be implemented as new renderer functions that reuse the same model queries and link to existing source dashboards for detail.

Which configs are needed on day one?

Recommended day-one config options:

  • hubIds :: [Id Hub] or hubFilter :: Maybe [Id Hub]
  • timeRange :: Last24Hours | Last7Days | Last30Days | AllTimeBounded
  • limit :: Int
  • sort :: NewestFirst | OldestFirst | HighestRiskFirst
  • displayMode :: Compact | Detailed

Config should be stored as JSONB but decoded into a Haskell ADT before use. Invalid config should fall back to panel defaults and surface a non-fatal operator warning.

Which panels should live-refresh?

Live-refresh in the first slice:

  • recent-interactions
  • triage-queue
  • hub-health
  • learning-digest

Static per request in the first slice:

  • watched-hubs
  • recent-decisions

If the first implementation wraps the entire personal dashboard in autoRefresh, all panels will refresh together. That is acceptable initially if queries are bounded.

How should saved panels map to governed widgets?

Each saved dashboard panel should own a widgets row and store the id on dashboard_panels.widget_id. The panel renderer should call widgetEnvelope with that widget. This gives stable annotation and interaction capture identity.

Panel lifecycle:

  1. User adds a panel.
  2. Controller creates dashboard_panels row.
  3. Controller creates linked widgets row with widget_type = 'panel'.
  4. Controller creates a widget_versions snapshot for the panel widget.
  5. Show view renders the panel through widgetEnvelope.
  6. Removing a panel should mark the widget archived or deprecated, not delete interaction history.

What should be deferred?

Defer:

  • Drag-and-drop layout.
  • Shared dashboards.
  • Team dashboards.
  • External datasource connectors.
  • Client-side data fetching.
  • Per-panel WebSocket channels.
  • Full refactor of existing dashboard views.
  • Complex role model.
  • Dashboard marketplace/templates beyond one seeded default.

Recommendations for T02/T03

  1. Define the personal dashboard as the authenticated landing page, not a replacement for existing source dashboards.
  2. Use a small panel catalogue for the first implementation.
  3. Persist dashboard/panel rows in relational tables and panel config in JSONB.
  4. Decode panel config into explicit Haskell ADTs before querying.
  5. Give every saved panel stable Widget identity.
  6. Use the existing panel widget type.
  7. Keep the default dashboard neutral and editable.
  8. Bound every panel query.
  9. Cast SQL aggregate counts to integer when decoding as Int.
  10. Keep implementation tasks small enough to avoid a cross-dashboard refactor.