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.
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.hsWeb/Controller/Sessions.hsWeb/FrontController.hsWeb/Routes.hsWeb/Types.hsApplication/Schema.sqlApplication/Helper/View.hsWeb/Controller/FederatedGovernance.hsWeb/Controller/FederatedPolicyOverlays.hsWeb/Controller/ApiDashboard.hsWeb/Controller/MarketplaceDashboard.hsWeb/Controller/LearningDashboard.hsdocs/phase1-summary.mdthroughdocs/phase8-summary.mddocs/ihp-ihf-mapping.mddocs/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:
ShowHubActionlimits recent interaction events to 50 and annotations to 20.GovernanceDashboardActionlimits recent decisions to 20.LearningDashboardActionlimits correlations, rankings, insights, and knowledge highlights.MarketplaceDashboardActionlimits 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 tointegeror decode asInt64. Recent production work exposed PostgreSQL/Haskell decode failures whenCOUNT(*)was decoded asInt.
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-iddata-widget-typedata-hub-iddata-capability-refdata-view-contextdata-policy-scopedata-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:
hubswidgetswidget_versionsinteraction_eventsannotationsannotation_threadsrequirement_candidatestriage_statesreviewer_assignmentsrequirementsdecision_recordsdeployment_recordsoutcome_signalsfriction_scoresbottleneck_recordshub_health_snapshotscross_hub_propagationswidget_ownershipshub_routing_rulesfederated_policy_overlaysstewardship_rolesarchive_recordswidget_type_registryevent_type_registryannotation_category_registrypolicy_scope_registryhub_capability_manifestsapi_consumersapi_request_logwidget_patternspattern_adoptionsgovernance_templatesgovernance_template_clonesoutcome_correlationspattern_performance_recordsadaptive_threshold_configsinstitutional_knowledge_entrieslearning_insights
Important gaps:
- No
personal_dashboardstable. - No
dashboard_panel_typestable. - No
dashboard_panelstable. - 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-proposalsapi-usagemarketplace-trendingmy-annotationsadapter-compatibilitypolicy-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
OperationalReviewBoardActionandHubHealthHistoryAction. - Learning digest from
LearningDashboardAction. - Watched hubs from
HubsActionplus 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]orhubFilter :: Maybe [Id Hub]timeRange :: Last24Hours | Last7Days | Last30Days | AllTimeBoundedlimit :: Intsort :: NewestFirst | OldestFirst | HighestRiskFirstdisplayMode :: 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-interactionstriage-queuehub-healthlearning-digest
Static per request in the first slice:
watched-hubsrecent-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:
- User adds a panel.
- Controller creates
dashboard_panelsrow. - Controller creates linked
widgetsrow withwidget_type = 'panel'. - Controller creates a
widget_versionssnapshot for the panel widget. - Show view renders the panel through
widgetEnvelope. - 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
- Define the personal dashboard as the authenticated landing page, not a replacement for existing source dashboards.
- Use a small panel catalogue for the first implementation.
- Persist dashboard/panel rows in relational tables and panel config in JSONB.
- Decode panel config into explicit Haskell ADTs before querying.
- Give every saved panel stable
Widgetidentity. - Use the existing
panelwidget type. - Keep the default dashboard neutral and editable.
- Bound every panel query.
- Cast SQL aggregate counts to integer when decoding as
Int. - Keep implementation tasks small enough to avoid a cross-dashboard refactor.