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

349 lines
14 KiB
Markdown

# 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`:
```haskell
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.