generated from coulomb/repo-seed
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.
349 lines
14 KiB
Markdown
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.
|