# Dashboards — PRD

## Status

In Progress

## Executive Summary

A grid-based dashboard builder for the Abacus royalties platform. Users create dashboards by placing chart, table, and markdown cards on a snapping grid, bind cards to reusable data sources backed by Snowflake or GraphQL, and share dashboards with colleagues. An AI prompt bar routes through the existing Coda agent loop to allow natural-language dashboard manipulation — adding cards, writing queries, rearranging layouts — while manual editing remains first-class.

## The Problem

Today, every analytical question in ows-coda flows through a single interface: chat. A user asks a question, the agent answers, and the response scrolls off the screen. There is no persistent, visual home for the metrics and datasets that users care about most.

Royalty analysts, account managers, and finance leads track recurring questions: advance recoupment rates across their top accounts, revenue by territory quarter over quarter, catalog performance trends. Right now, they either re-ask these questions in chat every time they need an update, or they export data to spreadsheets and build their own dashboards in Excel or Google Sheets. The AI did the hard work of fetching and computing the data, but the result has no permanent home inside the product.

This creates three problems:

- **No persistent visualization.** Chat is ephemeral. An answer from last Tuesday is buried in scroll history. Users cannot pin, arrange, or revisit their most important data views without re-asking.
- **No shared context.** When a manager wants to see the same metrics an analyst pulled, they either ask the analyst to forward a screenshot or re-run the same questions themselves. There is no shared surface.
- **No layout control.** Chat is linear. Users cannot place three charts side by side to compare catalogs, or put a summary table above a trend line. The interface dictates the layout, not the user.

Dashboards give users a canvas — a place to arrange, persist, share, and revisit the data that matters to them, powered by the same AI and data infrastructure that already exists.

## Proposed Solution

A dashboard builder that lets users create grid-based layouts of cards (charts, tables, markdown), each optionally bound to a reusable data source. Dashboards are shareable, starrable, copyable, and commentable. An AI prompt bar at the bottom provides natural-language editing as an accelerator on top of direct manipulation.

### Key components

- **Grid canvas** — `react-grid-layout` provides drag-and-drop, resize, and snap-to-grid. Layout stored as a JSON array `[{ i, x, y, w, h }]` in the database.
- **Cards** — three types: chart (recharts `ComposedChart` supporting bar, line, area, dual Y-axis, stacked series), table (TanStack Table with grouping), and markdown (scrollable rendered content).
- **Data sources** — user-owned source library. A `DataSource` belongs to a user, not a dashboard. Multiple dashboards can reference the same source via a join table, sharing one cache. Sources support Snowflake and GraphQL, with configurable cache strategies (manual or scheduled).
- **Sharing** — internal only (must be logged in). Two layers, most privilege wins: org-wide baseline (`publicRole`) and per-user grants (`DashboardShare`). Roles: `view`, `comment`, `edit`.
- **Comments** — single-level threading. Top-level comments can have replies, but replies cannot have replies (enforced at app layer). Soft-deletable with placeholder preservation.
- **AI prompt bar** — minimizable bar at the bottom of the dashboard page. Routes through the existing server agent loop (Bedrock, tool-use, Redis, SSE). Dashboard state passed as context. Agent returns structured tool calls (`add_card`, `update_card`, `update_layout`, etc.) applied as patches.
- **Edit vs view mode** — explicit toggle. Drag handles, resize, config panel only visible in edit mode. Viewers see a clean read-only layout.

## Key Decisions

| Decision              | Choice                          | Rationale                                                                                                                                                                          |
| --------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Grid engine           | `react-grid-layout`             | Purpose-built drag/resize/snap grid used by Grafana and Retool. Layout is a simple JSON array. One dependency (~45kb).                                                             |
| Chart library         | `recharts` (`ComposedChart`)    | Handles bar, line, area, and mixed charts in one component. Built-in dual Y-axis, reference lines, brush/zoom, stacked series.                                                     |
| AI prompt bar         | Reuse existing agent loop       | No new AI infrastructure. Dashboard state as context, structured tool calls as output, SSE stream reused from chat.                                                                |
| Data source ownership | User-owned, not dashboard-owned | Sources belong to a user's library. Dashboards reference them via a join table. One source, one cache, many dashboards. Copying a source from another user creates a new row.      |
| Sharing model         | `max(publicRole, perUserShare)` | Simple two-layer model. Org-wide baseline plus per-user overrides. Most privilege wins. Owner always gets `edit`.                                                                  |
| Comments              | Single-level threading          | Replies to top-level comments only. Keeps the UI simple. Soft-delete with placeholder to preserve thread structure.                                                                |
| Concurrent editing    | Last-write-wins for v1          | Layout saves are full state (not diffs), so any save is self-consistent. Dashboard editing is low-concurrency. Version-based conflict detection deferred to post-launch if needed. |

## Data Model

Six new Prisma models:

- **`Dashboard`** — title, ownerId, gridSize, layout (JSON), publicRole, soft-delete. Owns cards, sources (join), shares, stars, comments.
- **`DashboardCard`** — dashboardId, type (`chart | table | markdown`), title, position (x, y, w, h, zIndex), config (JSON), optional sourceId binding.
- **`DataSource`** — ownerId, name, type (`snowflake | graphql`), connectionConfig (encrypted at rest), query, runAs (`owner | viewer`), cacheStrategy (`manual | scheduled`), cacheInterval, cachedResult, lastRefreshedAt, expiresAt.
- **`DataSourceError`** — sourceId, type (`timeout | auth | syntax | unknown`), message, createdAt. Indexed by `[sourceId, createdAt]`. Purged after 30 days.
- **`DashboardSource`** — join table binding sources to dashboards. Unique on `[dashboardId, sourceId]`.
- **`DashboardShare`** — per-user role grants. Unique on `[dashboardId, userId]`.
- **`DashboardStar`** — user bookmark. Unique on `[dashboardId, userId]`.
- **`DashboardComment`** — body, parentId (single-level threading), soft-delete.

Supporting enums: `Role` (view, comment, edit), `CardType` (chart, table, markdown), `SourceType` (snowflake, graphql), `RunAs` (owner, viewer), `CacheStrategy` (manual, scheduled).

## User Experience

### Creating a dashboard

User navigates to `/dashboards` and clicks "New dashboard." An empty grid canvas appears with an inline-editable title. The edit toggle is on by default for new dashboards.

### Adding cards

In edit mode, user clicks "Add card" in the toolbar and selects a type (chart, table, or markdown). A new card appears on the grid at the first available position. User can drag to reposition, resize by pulling corners, and open the config panel (right-side slide-out) to configure the card's data source, chart settings, table columns, or markdown content.

### Data sources

User opens the source manager (drawer) to see their source library. They can create new sources (query editor + connection config + cache settings), bind existing sources to cards, or copy a source from an accessible dashboard into their library. Sources display their cache status and last refresh time.

### Sharing

Dashboard owner clicks "Share" in the toolbar. A modal allows setting the org-wide baseline role (none, view, comment, edit) and adding per-user grants. Users with `view` access see the dashboard read-only. Users with `comment` can add comments. Users with `edit` can modify cards and layout.

### AI-assisted editing

In edit mode, the prompt bar at the bottom accepts natural-language instructions: "Add a bar chart showing revenue by territory for Q4," "Move the summary table to the top left," "Create a Snowflake query that pulls advance balances for my top 10 accounts." The agent receives the current dashboard state as context and returns tool calls that patch the dashboard.

### Commenting

A comments panel (drawer or sidebar) shows top-level comments with their replies. Users with `comment` or `edit` role can post. Authors can edit or delete their own comments. Users with `edit` role can delete any comment. Deleted comments show a placeholder to preserve thread structure.

## Server API

All endpoints prefixed with `/api/v1`. Key groups:

- **Dashboard CRUD** — list, create, get, patch, soft-delete, copy, star toggle
- **Cards** — create, patch, delete (all require `edit` role)
- **Sources** — user's source library CRUD, refresh, get cached data, bind/unbind to dashboards
- **Shares** — list, grant, revoke (owner or `edit` role)
- **Comments** — list, create, edit, soft-delete (role-gated)

Access control via `resolveRole(userId, dashboard)` on every endpoint. Owner always gets `edit`. Effective role = `max(publicRole, perUserShare)`. Soft-deleted dashboards return null role.

Source data caching: `GET /sources/:id/data` returns cached result if fresh, auto-refreshes if stale (scheduled strategy), or requires explicit `POST /refresh` (manual strategy). On query failure, the existing cached result is preserved and an error row is logged.

## Technical Notes

- **DB models**: `@coda/db` (Prisma schema + migrations)
- **API types**: `@coda/core-api` (shared request/response types, client methods)
- **Client**: `apps/client/src/pages/dashboards/` (pages), `apps/client/src/components/dashboard/` (grid, cards, config, sources, toolbar, prompt, comments), `apps/client/src/hooks/` (use-dashboard, use-source-data, use-optimistic-layout)
- **Agent tools**: `get_dashboard_state`, `add_card`, `update_card`, `remove_card`, `add_source`, `update_source`, `bind_source`, `refresh_source`, `update_layout` — registered when the prompt bar initiates an agent session
- **Error handling**: per-card error boundaries, optimistic layout with rollback on failure, source query errors preserve stale data, permission errors surface as 403 with role context

## Dependencies

| Dependency                                | Status         | Notes                                    |
| ----------------------------------------- | -------------- | ---------------------------------------- |
| `react-grid-layout`                       | New dependency | Grid engine for drag/resize/snap (~45kb) |
| `recharts`                                | New dependency | Chart rendering (ComposedChart)          |
| `@tanstack/react-table`                   | Already in use | Table card rendering with grouping       |
| Existing agent loop (Bedrock, Redis, SSE) | Already in use | Prompt bar reuses chat infrastructure    |
| Prisma / Aurora MySQL                     | Already in use | Dashboard data model                     |
| Auth middleware                           | Already in use | All endpoints inherit existing auth      |

## Risks & Mitigations

| Risk                                                           | Likelihood | Impact | Mitigation                                                                                                                                                                           |
| -------------------------------------------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Grid layout performance degrades with many cards               | Low        | Medium | react-grid-layout handles 50+ items in production (Grafana). Paginate or virtualize if dashboards grow beyond expected sizes.                                                        |
| Source query cost — expensive Snowflake queries run frequently | Medium     | Medium | Cache strategy with configurable intervals (5-60 min). Manual strategy for expensive queries. `DataSourceError` logging for monitoring.                                              |
| Concurrent editing conflicts                                   | Low        | Low    | Last-write-wins for v1. Full-state saves are self-consistent. Add version column + 409 conflict response if this becomes a real problem post-launch.                                 |
| AI prompt bar generates invalid dashboard patches              | Medium     | Low    | Agent tool results are validated server-side (Zod schemas). Invalid patches return `is_error: true` and the agent can retry or explain. Manual editing always available as fallback. |
| Sharing permissions confusion                                  | Low        | Medium | Simple two-layer model with clear precedence. UI disables controls based on effective role rather than hiding the page. Toast on forbidden actions.                                  |

## Open Questions

1. **Dashboard templates.** Should we ship starter templates (e.g., "Revenue Overview," "Advance Recoupment Tracker") to reduce blank-canvas friction? Deferred to post-v1 based on usage patterns.
2. **Real-time collaboration.** v1 is single-editor with last-write-wins. If multiple users frequently edit the same dashboard, we may need operational transforms or CRDT-based merging. Monitor concurrent edit patterns post-launch.
3. **Dashboard versioning / history.** Should users be able to revert a dashboard to a previous state? Currently out of scope. Could be added by snapshotting layout + cards on each save.
4. **Source query sandboxing.** Snowflake queries run with the source owner's or viewer's credentials (`runAs`). Should there be query cost limits, row count caps, or query review for expensive sources?
5. **Mobile / responsive layout.** react-grid-layout supports responsive breakpoints. Should v1 include a mobile-friendly layout, or is desktop-only acceptable for the initial launch?
6. **Export.** Should dashboards be exportable as PDF or image for offline sharing? Deferred to post-v1.
