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:
2026-06-21 16:11:37 +02:00
parent a8292b639b
commit d6b655a5cf
5 changed files with 2227 additions and 191 deletions

View File

@@ -4,11 +4,11 @@ type: workplan
title: "Personal Dashboard Framework"
domain: inter_hub
repo: inter-hub
status: backlog
status: finished
owner: tegwick
topic_slug: inter_hub
created: "2026-05-03"
updated: "2026-06-07"
updated: "2026-06-16"
phase: 13
state_hub_workstream_id: "72fc022b-0196-492a-aaba-3475f8768f06"
---
@@ -17,283 +17,399 @@ state_hub_workstream_id: "72fc022b-0196-492a-aaba-3475f8768f06"
## Goal
Design and implement a personal dashboard framework that allows individual users to
compose, configure, and persist a view of the inter-hub platform tailored to their
role and focus. The dashboard is the post-login landing page and the primary daily
driver surface for hub operators, governance reviewers, and AI orchestrators.
Design the first personal dashboard layer for inter-hub: an authenticated,
per-user landing surface that composes the most important existing hub,
governance, API, marketplace, and learning signals into a configurable daily
operator view.
## Motivation
This workplan is now a design and implementation-planning workplan. It should
produce the current-state audit, product requirements, functional design, and
follow-on implementation workplan needed to build the feature safely.
The current post-login experience drops the user on a raw Hubs list. As inter-hub
grows to 12+ phases of functionality, users need a curated, role-aware entry point
that surfaces the signals that matter to them without requiring manual navigation.
The dashboard is also the natural home for cross-cutting observability (recent
decisions, open candidates, outcome signals) that cuts across the current
controller-per-entity navigation.
## Review Update: 2026-06-15
---
This workplan was reviewed against the current repository state and updated
from `backlog` to `ready`.
The original version assumed inter-hub mainly had a raw Hubs list and needed a
greenfield dashboard framework. That assumption is outdated. The repo now has
many dashboard-like surfaces and governed interaction primitives that should be
reused instead of bypassed:
- Public root/intro pages exist from IHUB-WP-0015; the authenticated login flow
still redirects to `HubsAction`.
- Hub-level dashboard actions already exist in `Web.Controller.Hubs`, including
hub show, triage, governance, antifragility, agent audit, adapter
compatibility, friction heatmap, bottleneck, hub health history, and the
operational review board.
- Cross-hub and platform dashboards already exist: federated governance, policy
compliance, API usage, marketplace, and learning dashboard.
- The governed interaction substrate is mature: `widgets`, `widget_versions`,
`interaction_events`, `annotations`, type registries, hub manifests,
ownership/routing, API request logs, hub health snapshots, learning insights,
and institutional knowledge are all present.
- There is no personal dashboard schema, controller, saved panel catalogue,
user preference model, or role-aware default layout yet.
- Existing dashboards are mostly hard-coded controller queries plus HSX view
fragments. They are useful source material, but they are not yet reusable
panel renderers.
- The `users` table has no role column. `stewardship_roles.assigned_to` is text
and hub-scoped, so role-aware defaults must be designed carefully instead of
assuming a user-role foreign key exists.
The updated scope is therefore integration-first: define a personal dashboard
contract that reuses existing data sources and view patterns, then introduce a
small panel renderer abstraction only where it removes real duplication.
## Scope
### In Scope
- Authenticated personal dashboard route and post-login redirect design.
- Per-user saved dashboard record with ordered panel instances.
- A server-rendered panel catalogue backed by existing inter-hub models.
- Simple layout editing through IHP forms; no drag-and-drop in the first slice.
- Hub/time filters for panels where the underlying queries already support
bounded data.
- Panel-level governance: each rendered saved panel must be annotatable and
event-capturable through the existing `widgetEnvelope` convention.
- A migration path that reuses current dashboard queries before attempting broad
refactors.
### Out of Scope for the First Implementation Workplan
- Client-side dashboard frameworks or client-side data fetching.
- External datasource connectors.
- Shared/team dashboards.
- Mobile-native layout editing.
- Drag-and-drop layout editing.
- A general purpose report builder.
- Rewriting every existing dashboard into panel renderers.
## Current Design Constraints
- Server-rendered IHP views remain the default. `autoRefresh` is acceptable for
panels that already use live refresh patterns.
- Tailwind and existing HSX view conventions should be reused.
- Runtime panel config may be stored as JSONB, but renderer code should decode
into explicit Haskell config types before use.
- Do not create an ungoverned visual component layer. A saved dashboard panel
must either reference or create a `Widget` row, most likely using the existing
framework-level `panel` widget type, so annotations and interaction events
remain first-class IHF artifacts.
- Avoid adding a `users.role` column unless the PRS/FDD proves it is needed.
Prefer defaults derived from current user identity, stewardship assignments,
selected watched hubs, or explicit dashboard template choice.
## Proposed First-Slice Panel Catalogue
The initial catalogue should be limited to panels that can be built from
existing tables and controllers:
| Panel key | Label | Source surface/data | Live? |
|---|---|---|---|
| `watched-hubs` | Watched Hubs | `hubs`, `hub_health_snapshots`, optional saved hub filter | No |
| `recent-interactions` | Recent Activity | `interaction_events` plus `widgets` and `hubs` | Yes |
| `triage-queue` | Triage Queue | open `requirement_candidates` | Yes |
| `recent-decisions` | Recent Decisions | `decision_records`, requirements, candidates | No |
| `hub-health` | Hub Health | latest `hub_health_snapshots`, bottlenecks | Yes |
| `agent-proposals` | Agent Proposals | `agent_proposals`, `agent_review_records` | No |
| `api-usage` | API Usage | `api_consumers`, `api_request_log` | Yes |
| `marketplace-trending` | Marketplace Trending | `widget_patterns`, adoptions, templates | No |
| `learning-digest` | Learning Digest | `learning_insights`, `institutional_knowledge_entries` | Yes |
| `my-annotations` | My Annotations | `annotations` filtered by current user when available | No |
The implementation workplan should start with a smaller subset if needed:
`watched-hubs`, `recent-interactions`, `triage-queue`, `recent-decisions`,
`hub-health`, and `learning-digest` are enough to prove the framework.
## Tasks
### T01 — Research: Dashboard frameworks and patterns for inspiration
### T01 - Current-state audit and dashboard pattern research
```task
id: IHUB-WP-0020-T01
status: todo
status: done
priority: high
state_hub_task_id: "6074f195-636b-4517-b6d1-eb3c57394a82"
```
Survey existing dashboard systems to extract patterns that are re-implementable in
Haskell / IHP / Tailwind under inter-hub's design constraints (server-rendered,
type-safe, governed).
Produce a short research note that starts with the current inter-hub codebase,
then uses external dashboard systems only for secondary inspiration.
**Research targets:**
Required current-state inventory:
- Existing routes and views that behave like dashboards.
- Existing `autoRefresh` usage and query patterns that are safe to reuse.
- Existing type registry, `Widget`, `widgetEnvelope`, annotation, and event
capture constraints.
- Existing tables that can power first-slice personal panels.
- Gaps: personal dashboard persistence, panel catalogue, saved filters, layout
model, and user preference/defaulting model.
External systems may still be sampled, but the output should focus on patterns
that are practical in IHP/HSX/Tailwind:
| System | What to extract |
|---|---|
| **Grafana** | Panel/grid layout model; datasource abstraction; variable-driven filtering |
| **Kibana (dashboards)** | Saved-search panels; time-range awareness; role-based visibility |
| **Retool / Appsmith** | Widget catalogue approach; drag-grid layout; data binding model |
| **Linear (home view)** | Flat "My Work" aggregation across entities; priority surfacing |
| **Notion (linked databases)** | Filter/sort persistence per user; view types (table, board, calendar) |
| **Observable Framework** | Reactive cell model; markdown + code co-location |
| **Metabase** | Question-as-unit; dashboard as ordered collection of questions |
| **Streamlit** | Declarative layout (columns, expanders); pure functional rendering loop |
| Grafana | Panel/grid layout model, dashboard variables, bounded refresh |
| Kibana dashboards | Saved-search panels, time range filters, role visibility |
| Retool/Appsmith | Widget catalogue and data binding concepts, not their client runtime |
| Linear home view | Flat "my work" aggregation across entities |
| Notion linked databases | Saved filters/sorts as user-facing views |
| Metabase | Question-as-unit model and governed saved queries |
| Streamlit | Declarative layout vocabulary suitable for server rendering |
**Questions to answer per system:**
1. How is a dashboard persisted? (JSON blob, relational rows, code-as-config?)
2. How is a widget/panel parameterised? (datasource, filter, display options)
3. How is layout described? (fixed grid, CSS grid, drag-and-drop, responsive breakpoints)
4. How is per-user state separated from shared/team state?
5. What is the update model? (full-page reload, WebSocket push, polling, partial HTMX swap)
6. How are access controls expressed at panel level?
Questions to answer:
**IHP/Haskell-specific constraints to keep in mind:**
- Server-rendered by default; AutoRefresh (WebSocket) available for live data
- No client-side state management library; JS must be minimal
- Type safety from DB schema → view layer is a first-class constraint
- Tailwind CSS; no component library
1. Which existing inter-hub dashboard fragments can become first-slice panel
renderers without broad refactoring?
2. Which panel configs must exist on day one: hub filter, time range, limit,
display mode, or sort order?
3. Which panels need live refresh, and which should stay static per request?
4. How should each saved panel map to a governed `Widget` row?
5. What should be explicitly deferred to avoid building a report builder?
**Exit criteria:** Research notes written in `docs/research/dashboard-frameworks.md`.
Exit criteria: `docs/research/personal-dashboard-current-state.md` exists and
has enough evidence to drive the PRS.
Completion note (2026-06-16): added
`docs/research/personal-dashboard-current-state.md`, covering the current
dashboard inventory, AutoRefresh/query patterns, governed widget constraints,
first-slice panel candidates, external pattern extraction, and T02/T03
recommendations.
---
### T02 Product Requirements Specification (PRS)
### T02 - Product Requirements Specification
```task
id: IHUB-WP-0020-T02
status: todo
status: done
priority: high
depends_on: T01
state_hub_task_id: "698304bc-b91a-42e2-a617-b3ddbf749174"
```
Produce a formal PRS for the Personal Dashboard Framework based on T01 findings and
inter-hub's design principles.
Produce a formal PRS based on T01 and the current implementation.
**Required sections:**
Required sections:
1. **Problem statement** — who uses the dashboard, what decisions they make with it,
what pain the current flat-nav approach causes
1. Problem statement: authenticated users currently land on the Hubs list and
must manually navigate to specialized dashboards to answer daily operating
questions.
2. Personas:
- Hub operator: watches hub health, recent events, candidates, and
bottlenecks.
- Governance reviewer: triages candidates, decisions, policy coverage, and
annotations.
- AI orchestrator: watches agent proposals, review outcomes, and learning
signals.
- Platform admin: watches API usage, hub registry health, manifests, and
cross-hub propagation.
3. Core requirements using MoSCoW:
- Must: per-user saved dashboard, seeded default dashboard, panel catalogue,
server-rendered panels, persisted layout, governed panel widget identity,
post-login route design, bounded panel queries.
- Should: hub/time filters, simple edit mode, live refresh on selected
panels, keyboard-accessible forms, link-outs to existing source
dashboards.
- Could: dashboard templates, saved watched-hub sets, shared dashboards,
richer display modes.
- Won't: drag-and-drop, external datasources, client-side fetching, mobile
layout editor, complete refactor of existing dashboards.
4. Non-functional requirements:
- First paint target remains sub-second for seeded dashboards with bounded
panel queries.
- Panel queries must use limits and existing indexes or propose new indexes.
- Dashboard save/load must be simple transactional IHP controller work.
- No new JS framework.
5. Governance fit:
- Saved panel instances are governed IHF widgets or reference governed
widgets.
- Panel views use `widgetEnvelope`.
- Panel interactions emit existing event types where possible.
- Annotations attach to the panel widget identity, not to a transient DOM
block.
2. **User personas**
- Hub Operator: monitors activity within their hub; wants recent events, open candidates
- Governance Reviewer: triages candidates, reviews decisions; needs queue and signal views
- AI Orchestrator: monitors agent proposals, outcome correlations; needs performance panels
- Platform Admin: watches system health, API usage, learning throughput
Exit criteria: `docs/prs/personal-dashboard-prs.md` exists and is ready for
FDD work.
3. **Core requirements (MoSCoW)**
- Must: per-user dashboard persisted in DB; selectable panels from a catalogue;
layout preserved across sessions; role-aware default layout on first login
- Should: panel-level filtering (by hub, by time range); live-update via AutoRefresh
for signal panels; keyboard-navigable
- Could: drag-and-drop layout editing; shared/team dashboards; dashboard templates
- Won't (Phase 13): mobile-native layout; client-side data fetching; external datasources
4. **Non-functional requirements**
- First paint < 500 ms (server-rendered, no JS data fetching)
- Dashboard save/load < 100 ms
- Each panel query < 200 ms (indexed, bounded result sets)
- Zero JS frameworks; AutoRefresh WebSocket for live panels only
5. **Governance fit** — dashboard widgets are themselves `Widget` records in the IHF
sense; `InteractionEvent`s recorded on dashboard interactions; annotations possible
on any panel
**Exit criteria:** `docs/prs/dashboard-framework-prs.md` reviewed and accepted.
Completion note (2026-06-16): added
`docs/prs/personal-dashboard-prs.md`, defining the problem statement,
personas, MoSCoW requirements, first-slice panel catalogue, governance
requirements, acceptance criteria, risks, and FDD open questions.
---
### T03 Functional Design Document (FDD)
### T03 - Functional Design Document
```task
id: IHUB-WP-0020-T03
status: todo
status: done
priority: high
depends_on: T02
state_hub_task_id: "438e5771-a043-4f26-a1ce-994ed478a760"
```
Translate the PRS into a concrete functional design covering schema, component model,
rendering pipeline, and layout system. This is the authoritative reference for implementation.
Translate the PRS into a concrete FDD covering schema, controller actions,
panel renderer contract, layout, seed/default behavior, and migration strategy.
**Required sections:**
#### 3.1 Data model
The FDD must update the old greenfield schema sketch. A likely shape is:
```sql
-- A user's named dashboard
CREATE TABLE dashboards (
CREATE TABLE personal_dashboards (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
user_id UUID NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- An instance of a panel type on a dashboard, with position
CREATE TABLE dashboard_panels (
CREATE TABLE dashboard_panel_types (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
dashboard_id UUID NOT NULL REFERENCES dashboards(id) ON DELETE CASCADE,
panel_type TEXT NOT NULL, -- FK to panel_type_registry
config JSONB NOT NULL DEFAULT '{}',
col INT NOT NULL DEFAULT 0,
row INT NOT NULL DEFAULT 0,
col_span INT NOT NULL DEFAULT 1,
row_span INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Registry of available panel types
CREATE TABLE panel_type_registry (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
name TEXT NOT NULL UNIQUE,
key TEXT NOT NULL UNIQUE,
label TEXT NOT NULL,
description TEXT,
default_config JSONB NOT NULL DEFAULT '{}',
requires_hub BOOLEAN NOT NULL DEFAULT FALSE,
live_update BOOLEAN NOT NULL DEFAULT FALSE
default_col_span INT NOT NULL DEFAULT 4,
default_row_span INT NOT NULL DEFAULT 1,
live_update BOOLEAN NOT NULL DEFAULT FALSE,
status TEXT NOT NULL DEFAULT 'active'
);
CREATE TABLE dashboard_panels (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
dashboard_id UUID NOT NULL REFERENCES personal_dashboards(id) ON DELETE CASCADE,
panel_type_id UUID NOT NULL REFERENCES dashboard_panel_types(id),
widget_id UUID NOT NULL REFERENCES widgets(id),
config JSONB NOT NULL DEFAULT '{}',
col INT NOT NULL DEFAULT 0,
row INT NOT NULL DEFAULT 0,
col_span INT NOT NULL DEFAULT 4,
row_span INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```
#### 3.2 Panel catalogue (Phase 13 scope)
The FDD must also resolve:
| Panel type name | Label | Description | Live? |
|---|---|---|---|
| `recent-interactions` | Recent Activity | Latest interaction events across watched hubs | Yes |
| `open-candidates` | Open Candidates | Requirement candidates awaiting triage | No |
| `decision-queue` | Decision Queue | Decisions pending review | No |
| `outcome-signals` | Outcome Signals | Recent outcome signal summary | Yes |
| `hub-health` | Hub Health | Health snapshot per hub | No |
| `agent-proposals` | Agent Proposals | Open AI agent proposals | No |
| `learning-digest` | Learning Digest | Latest institutional knowledge entries | No |
| `my-annotations` | My Annotations | Annotations by the current user | No |
- Naming: whether tables should use `personal_dashboards` or another prefix to
avoid confusing them with existing dashboard actions.
- Panel config: JSONB storage plus explicit Haskell ADT decoding and validation.
- Governance identity: how `dashboard_panels.widget_id` is created, versioned,
and named.
- Renderer contract:
#### 3.3 Layout system
12-column CSS grid; panels occupy `col_span` columns and `row_span` rows.
Row height fixed at 240px. No drag-and-drop in Phase 13; layout edited via
form fields (col, row, span). Responsive: collapse to single column below 768px.
```
┌─────────────────────────────────────────────────────┐
│ [Recent Activity ×6] [Decision Queue ×3] [Hub ×3]│
│ │
├─────────────────────────────────────────────────────┤
│ [Open Candidates ×4] [Outcome Signals ×4] [My ×4] │
└─────────────────────────────────────────────────────┘
```
#### 3.4 Rendering pipeline
```
GET /Dashboard
→ DashboardController#show
→ fetch dashboard + panels (ordered by row, col)
→ for each panel: dispatch to panel renderer (panelHtml panelType config)
→ embed in DashboardView with CSS grid layout
→ AutoRefresh wraps live panels only
```
Each panel renderer is a Haskell function:
```haskell
type PanelRenderer = PanelConfig -> ModelContext -> IO Html
renderPanel :: Text -> PanelConfig -> ModelContext -> IO Html
renderPanel "recent-interactions" = renderRecentInteractions
renderPanel "open-candidates" = renderOpenCandidates
...
data DashboardPanelConfig
= WatchedHubsConfig WatchedHubsOptions
| RecentInteractionsConfig RecentInteractionsOptions
| TriageQueueConfig TriageQueueOptions
| RecentDecisionsConfig RecentDecisionsOptions
| HubHealthConfig HubHealthOptions
| LearningDigestConfig LearningDigestOptions
renderDashboardPanel
:: DashboardPanelType
-> DashboardPanel
-> DashboardPanelConfig
-> ModelContext
-> IO Html
```
#### 3.5 Edit flow
- Layout: 12-column grid on desktop, single-column below the existing Tailwind
breakpoint, stable row/span constraints, no drag-and-drop in the first slice.
- Routes/actions:
- `PersonalDashboardAction`
- `EditPersonalDashboardAction`
- `UpdatePersonalDashboardAction`
- `AddDashboardPanelAction`
- `UpdateDashboardPanelAction`
- `RemoveDashboardPanelAction`
- Login behavior: `CreateSessionAction` should redirect to the personal
dashboard after authentication, while public root pages remain unchanged.
- Defaulting model: seed a default dashboard on first visit without requiring a
`users.role` column.
- Query safety: each panel query must be bounded, indexed, and compatible with
current PostgreSQL type decoding practices such as casting `COUNT(*)` to
integer when read as `Int`.
- Tests and smoke checks needed for the follow-on implementation workplan.
`GET /Dashboard/Edit` → grid with inline forms per panel (col/row/span inputs) +
"Add Panel" dropdown from `panel_type_registry`. `POST /Dashboard` saves layout.
No JavaScript needed for basic edit; optional HTMX for panel preview.
Exit criteria: `docs/fdd/personal-dashboard-fdd.md` exists, schema decisions
are concrete enough to implement, and open questions are explicitly listed.
#### 3.6 Default dashboard on first login
`afterLoginRedirectPath` (in `SessionsControllerConfig`) redirects to `/Dashboard`.
On first visit, `DashboardController` checks for an existing dashboard; if absent,
creates a default one seeded from the user's role (determined by a `role` field
to be added to `users`, or a simple heuristic based on existing data).
**Exit criteria:** FDD reviewed; schema migrations drafted; panel catalogue agreed.
Completion note (2026-06-16): added
`docs/fdd/personal-dashboard-fdd.md`, resolving schema names, panel config
typing, renderer/view-model shape, default seeding, governed panel widget
lifecycle, query constraints, routes, layout, tests, and handoff shape for
IHUB-WP-0021.
---
### T04 Implementation workplan
### T04 - Implementation workplan
```task
id: IHUB-WP-0020-T04
status: todo
status: done
priority: medium
depends_on: T03
state_hub_task_id: "970aa221-7e17-4500-8b37-9c98676280b1"
```
Break T03's FDD into a detailed, sequenced task list suitable for execution as a new
workplan (IHUB-WP-0021). Each task must have a clear entry/exit criterion and fit within
the 8k token soft budget.
Create the execution workplan for implementation as `IHUB-WP-0021`.
**Expected task structure of IHUB-WP-0021:**
Expected task structure for `IHUB-WP-0021`:
```
T01 Schema migration: dashboards, dashboard_panels, panel_type_registry
T02 Seed: default panel types in panel_type_registry
T03 DashboardController — show action (fetch + render)
T04 Panel renderers — first 3 panels (recent-interactions, open-candidates, decision-queue)
T05 DashboardView — CSS grid layout
T06 Panel renderers — remaining 5 panels
T07 Dashboard edit flow (layout form, add/remove panels)
T08 Default dashboard seeding on first login
T09 afterLoginRedirectPath → /Dashboard
T10 AutoRefresh for live panels (recent-interactions, outcome-signals)
T11 Role-aware default layout
T12 Smoke tests
```
| Task | Focus |
|---|---|
| T01 | Schema migration for personal dashboards, panel types, and panel instances |
| T02 | Seed dashboard panel types and any required framework `panel` widgets/type vocabulary |
| T03 | Add controller/action/route skeleton and default dashboard lookup/seed helper |
| T04 | Implement first three renderers: watched hubs, recent interactions, triage queue |
| T05 | Implement dashboard show view and responsive CSS grid |
| T06 | Implement remaining first-slice renderers: recent decisions, hub health, learning digest |
| T07 | Implement edit flow: reorder/update layout, add/remove panels, validate config |
| T08 | Add governed widget identity creation and `widgetEnvelope` wrapping for panels |
| T09 | Redirect successful login to the personal dashboard |
| T10 | Add `autoRefresh` only around selected live panels or the whole page if finer wrapping is not practical |
| T11 | Add focused tests for seeding, panel config validation, route access, and bounded queries |
| T12 | Manual smoke: login, seeded dashboard, edit layout, annotate a panel, verify source dashboards still load |
**Exit criteria:** IHUB-WP-0021 workplan file committed; T01T12 each have
entry/exit criteria; ready for execution.
Each task must have entry criteria, exit criteria, rollback notes, and the
smallest reasonable test/smoke requirement. Keep implementation slices small
enough for Codex sessions to finish without broad refactors.
---
Exit criteria: `workplans/IHUB-WP-0021-personal-dashboard-implementation.md`
exists with all tasks in `todo` state and enough detail to start implementation.
Completion note (2026-06-16): added
`workplans/IHUB-WP-0021-personal-dashboard-implementation.md` with twelve
sequenced implementation tasks covering schema, seeds, controller skeleton,
panel renderers, show/edit views, governed panel widget lifecycle, login
redirect, AutoRefresh/query hardening, tests, and manual smoke.
## Exit Criteria Summary
| Task | Deliverable | Status |
|---|---|---|
| T01 | `docs/research/dashboard-frameworks.md` | todo |
| T02 | `docs/prs/dashboard-framework-prs.md` | todo |
| T03 | `docs/fdd/dashboard-framework-fdd.md` | todo |
| T04 | `workplans/IHUB-WP-0021-personal-dashboard-impl.md` | todo |
| T01 | `docs/research/personal-dashboard-current-state.md` | done |
| T02 | `docs/prs/personal-dashboard-prs.md` | done |
| T03 | `docs/fdd/personal-dashboard-fdd.md` | done |
| T04 | `workplans/IHUB-WP-0021-personal-dashboard-implementation.md` | done |
## Design Principles (binding throughout)
## Binding Design Principles
- **Server-first**: every panel renders in a single round-trip. No client-side data fetching.
- **Type-safe config**: `PanelConfig` is a Haskell ADT, not an opaque JSON blob at runtime.
- **IHF governed**: each rendered panel is a `Widget` with a `widgetEnvelope`; interactions
are recorded; annotations can be attached.
- **Tailwind only**: no external component library. Layout via CSS Grid with inline style for
structural properties (lessons learned from sidebar nav).
- **Minimal JS**: AutoRefresh WebSocket for live panels; vanilla JS for any UX enhancement.
No framework, no bundler beyond the existing IHP asset pipeline.
- Server-first: every panel renders on the server in the normal IHP request
lifecycle.
- Integration-first: reuse current dashboard query patterns before extracting
shared abstractions.
- Governed panels: saved panel instances have stable IHF widget identity and
use `widgetEnvelope`.
- Type-safe runtime config: JSONB is storage, not the unchecked runtime API.
- Bounded queries: every panel limits rows and uses existing indexes or proposes
a specific migration.
- Minimal JS: no framework and no client-side data fetch loop.
- Tailwind only: use existing view style and responsive grid conventions.