generated from coulomb/repo-seed
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.
This commit is contained in:
348
docs/research/personal-dashboard-current-state.md
Normal file
348
docs/research/personal-dashboard-current-state.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user