Skip to content
Back to skills

Dashboards

ASecurity

Create interactive web dashboards to visualize data and provide UI elements to the user

  • 24 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
designjavascriptrustjavabashreactdebuggingapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 14 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add SkillfulAgents/SuperAgent --skill dashboards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dashboards?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dashboards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/skillfulagents-dashboards/badge)](https://www.skillsdirectory.com/skills/skillfulagents-dashboards)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dashboards
description: Create interactive web dashboards to visualize data and provide UI elements to the user
---

# Creating Dashboards

You can create web dashboards that are served to the user through the Gamut UI. Dashboards are full web applications (HTML/JS/React/Svelte/etc.) that run as servers inside the container.

## When to Build a Dashboard

Build one when the user needs a rich, reusable visual artifact rather than a chat reply or a static downloadable file — interactive charts, filters, or tables; a tracker, calculator, or data explorer; a multi-view report with controls; a visual interface over generated or fetched data. A one-off number or table belongs in the chat response instead.

## Available Tools

- **`create_dashboard`** — Scaffold a new dashboard project with the correct structure and boilerplate
- **`start_dashboard`** — Start a dashboard server (or restart it after code changes)
- **`list_dashboards`** — List all dashboards and their status
- **`get_dashboard_logs`** — Read stdout/stderr logs from a dashboard (useful for debugging)
- **Browser tools** — Open the running dashboard with `browser_open(..., location="container")`, inspect its rendered/accessibility state, exercise controls, and check client-side errors

## Quick Start (React)

1. Copy the template: `cp -r /opt/gamut/plugin/skills/dashboards/templates/react-vite /workspace/artifacts/<slug>`
2. Update `package.json` with the dashboard's `name` and `description`
3. Edit `src/App.jsx` to build the UI (add API routes in `serve.js` if needed). Build dashboard-scoped URLs with `window.__GAMUT_DASHBOARD__.url('api/data')`.
4. Use `start_dashboard` to build and start the server; inspect its screenshot and note the returned port
5. Open `http://localhost:<port>` with `browser_open(..., location="container")`, exercise the important interactions, and check browser errors
6. Iterate until both visual and functional checks pass; close the browser when validation is complete

## Directory Structure

Dashboards live in `/workspace/artifacts/<slug>/`:

```
/workspace/artifacts/my-dashboard/
├── package.json        # Must have name, description, and start script
├── index.js            # Entry point (for plain dashboards)
├── tokens.css          # Design tokens (plain dashboards; see DESIGN.md)
└── dashboard.log       # Auto-generated stdout/stderr log
```

## Requirements

- **`package.json`** must have `name`, `description`, and a `start` script
- The server **must listen on the port provided via `DASHBOARD_PORT` environment variable**
- Use `bun` as the runtime (it's pre-installed)

## Plain Dashboard Example (Bun.serve)

```javascript
// index.js
const port = process.env.DASHBOARD_PORT || 3000;

const server = Bun.serve({
  port,
  fetch(req) {
    return new Response(`
      <!DOCTYPE html>
      <html>
        <body>
          <h1>My Dashboard</h1>
          <div id="chart"></div>
          <script>
            // Your interactive JavaScript here
          </script>
        </body>
      </html>
    `, { headers: { 'Content-Type': 'text/html' } });
  },
});

console.log(`Server running on port ${port}`);
```

## React Dashboard (Recommended)

A pre-configured React + Vite template is available. To create a new React dashboard:

```bash
cp -r /opt/gamut/plugin/skills/dashboards/templates/react-vite /workspace/artifacts/<slug>
```

Then update `package.json` with the dashboard's `name` and `description`, and edit `src/App.jsx` to build the UI.

The template structure:

```
/workspace/artifacts/<slug>/
├── package.json        # Update name + description
├── gamut-dashboard.js  # Gamut base-path + Vite/HMR adapter
├── vite.config.js      # Uses the Gamut adapter
├── serve.js            # Static server with API route support
├── index.html
└── src/
    ├── main.jsx
    ├── tokens.css       # Design tokens (see DESIGN.md)
    └── App.jsx          # Edit this to build your dashboard
```

React dashboards are **built to static files** (`vite build`) and served via `serve.js` by default. The start script runs `bun run build-if-needed.js && bun run serve.js` — the build is skipped when no source file is newer than `dist/` (container restarts reuse the previous build; set `DASHBOARD_FORCE_BUILD=1` to force one). The included adapter also supports the Vite dev server and keeps HMR beneath the artifact mount.

**CRITICAL:** Keep `gamutDashboard()` in `vite.config.js`. The dashboard manager supplies `DASHBOARD_BASE_PATH`; in development the adapter applies it to Vite's entry modules and HMR client. Production assets, dynamic imports, CSS/worker URLs, fonts, and images remain relative, while `serve.js` and the injected runtime provide a stable document/router base. This keeps one production build relocatable.

### Adding API routes to a React dashboard

Edit `serve.js` to add API routes inside the `fetch` handler, before the static file fallback:

```javascript
// In serve.js, inside the fetch handler:
if (url.pathname === '/api/data') {
  const data = { items: [1, 2, 3] };
  return Response.json(data);
}

// Static files are served automatically for all other paths
return serveStatic(req, url.pathname);
```

## URL Paths & Proxying

Dashboards are served through a proxy chain:
```
Browser → Main App (/api/agents/:id/artifacts/:slug/) → Container → Dashboard Server
```

Gamut always communicates the public artifact mount through `DASHBOARD_BASE_PATH` in the process and `window.__GAMUT_DASHBOARD__` in browser HTML. The default `stripped` mode also sends `X-Forwarded-Prefix` on HTTP/WebSocket requests. The opt-in `mounted` mode retains the prefix in the upstream request path and omits that header on the final dashboard hop to avoid applying the prefix twice.

Use the injected helper for dashboard-owned URLs. It stays correct on nested client routes and when another trusted proxy adds its own prefix:

```javascript
const { basePath, routerBasePath, url } = window.__GAMUT_DASHBOARD__;

fetch(url('api/data'));
image.src = url('assets/chart.png');

// React Router and similar SPA routers need the runtime router base, which is
// intentionally separate from Vite's build-time asset base.
// <BrowserRouter basename={routerBasePath}>...</BrowserRouter>
```

Root-relative application URLs such as `/api/data` still target the Gamut host and must not be used for dashboard-owned routes.

### Third-party Vite apps and OpenSlide

Frameworks that accept a base at startup can consume the manager-provided value directly:

```ts
// open-slide.config.ts
import type { OpenSlideConfig } from '@open-slide/core';

const config: OpenSlideConfig = {
  // OpenSlide uses this value for both Vite assets and BrowserRouter.
  base: process.env.DASHBOARD_BASE_PATH || '/',
  port: Number(process.env.DASHBOARD_PORT) || 5173,
};

export default config;
```

OpenSlide's built-in Vite configuration cannot install Gamut's prefix-restoring
middleware. Opt its dashboard server into mounted upstream paths so HTTP modules
and HMR WebSockets both reach Vite beneath the same absolute base:

```json
{
  "scripts": { "start": "open-slide dev" },
  "gamut": { "upstreamPath": "mounted" }
}
```

The default upstream-path mode is `stripped`, which keeps existing dashboard
servers receiving root-local paths such as `/api/data`. Use `mounted` only when
the framework requires inbound requests to retain `DASHBOARD_BASE_PATH`.

For an app-owned React Router, prefer the injected runtime value:

```tsx
<BrowserRouter basename={window.__GAMUT_DASHBOARD__?.routerBasePath ?? '/'}>
  <App />
</BrowserRouter>
```

Do not patch generated bundles after Vite hashes them and do not add a query string to only one copy of an entry module. Both approaches violate module/cache identity and can leave stale router code or duplicate React instances.

## Interactive Validation

- **Validate every dashboard in container Chromium.** After `start_dashboard`, open the exact localhost URL it returns with `browser_open(..., location="container")`. Mounted dashboards include `DASHBOARD_BASE_PATH` in that URL; do not trim it back to `/`. The explicit location forces the bundled browser that can reach private container ports.
- **Test behavior, not just appearance.** Exercise the primary controls and workflows, inspect rendered and accessibility state, and check browser console/errors for client-side failures.
- **Use both diagnostic surfaces.** Use `get_dashboard_logs` for server failures and browser diagnostics for rendering or client-side failures.
- **Close the browser when validation is complete.**

## Exposing a Widget

A dashboard can also expose a **widget**: a small glanceable card on the user's home screens that stands in for the dashboard's screenshot and opens the dashboard when tapped. It is a static `widget.html` rewritten by a `widget.ts` script (which can import this dashboard's own data helpers) — no server involved, refreshed by the platform when the snapshot's `validUntil` passes and the user looks. Call `create_widget` with this dashboard's slug and load the `widgets` skill for the contract (sizing, dark mode, `widget.json` validity, review checklist). Offer one whenever the dashboard has a headline number or a "what's next" the user would want at a glance.

## Built-in APIs

The following APIs are automatically available in all dashboards (injected by the platform):

- **Speech Recognition** — The standard `SpeechRecognition` Web API for voice-to-text. See `/opt/gamut/plugin/skills/dashboards/SPEECH_RECOGNITION.md` for full documentation and examples.
- **LLM (Anthropic SDK)** — An Anthropic SDK-compatible `Anthropic` client for calling Claude. No API keys needed. See `/opt/gamut/plugin/skills/dashboards/LLM_API.md` for full documentation and examples.
- **Session Dispatch** — `window.__GAMUT_DASHBOARD__.dispatchSession({ prompt, title? })` asks the app to start a new session on this dashboard's own agent. The app always shows the user a confirmation popup first (with the prompt editable), so wire it to explicit user actions like buttons — never call it automatically. See `/opt/gamut/plugin/skills/dashboards/SESSION_DISPATCH.md` for full documentation and examples.

## Best Practices

- **Keep dependencies minimal** — fewer deps means faster installs and starts
- **Always use `process.env.DASHBOARD_PORT`** — never hardcode ports
- **Use the dashboard URL helper** — `window.__GAMUT_DASHBOARD__.url(...)` keeps URLs inside the mount
- **Use Bun APIs** — `Bun.serve()`, `Bun.file()`, etc. are fast and built-in
- **Check logs on errors** — use `get_dashboard_logs` to debug crashes
- **Restart after changes** — use `start_dashboard` after modifying source code
- **Verify interactively** — use `browser_open(..., location="container")` after every restart and exercise the changed behavior
- **Static assets** — serve them from the same directory or use inline styles/scripts for simplicity

## Design and Accessibility

- **Follow the design system in `/opt/gamut/plugin/skills/dashboards/DESIGN.md`** — read it before building UI. Both scaffolds ship its tokens as `tokens.css`: build with those variables and `.sa-*` classes rather than defining your own palette, so the dashboard matches the app in light and dark mode.
- Build mobile-first responsive layouts with grid or flexbox.
- Establish hierarchy through typography, spacing, and grouping.
- Meet WCAG AA contrast and never communicate state through color alone.
- Use semantic HTML, proper headings, and associated labels; reach for ARIA only when native semantics are insufficient.
- Provide loading, empty, error, and stale-data states rather than blank areas.
- Keep dependencies proportional — a simple visualization does not need a large application framework.
- Make important metrics understandable without requiring hover.
- For charts, pick a representation that matches the question and keep axes, units, legends, and tooltips unambiguous. Test interaction and keyboard access, not just the initial render.

## Common Failure Causes

- not listening on `DASHBOARD_PORT`;
- failing to install a newly added dependency;
- using root-relative browser URLs instead of the injected helper;
- using the host browser for a private container URL;
- configuring a router with `/` rather than `routerBasePath`;
- assuming the screenshot proves controls or client routing work;
- restarting repeatedly without fixing a crash loop's root cause.

Clear logs before a fresh reproduction when old output makes diagnosis ambiguous.

## Completion Checklist

- The dashboard starts successfully after the final change.
- The returned screenshot has no obvious layout or content failure.
- The exact returned URL works in container Chromium.
- Primary controls and routes were exercised.
- Loading, empty, and error behavior are present where relevant.
- Browser diagnostics and server logs contain no unexplained errors.
- Dashboard-owned URLs use the runtime helper.
- The validation browser is closed when no longer needed.
- The final response names the dashboard slug and current status.

Files in this skill

  • DESIGN.md8.3 KB
  • LLM_API.md6.4 KB
  • SESSION_DISPATCH.md4.3 KB
  • SKILL.md12.9 KB
  • SPEECH_RECOGNITION.md5.9 KB
  • templates/react-vite/build-if-needed.js1.9 KB
  • templates/react-vite/gamut-dashboard.js3.5 KB
  • templates/react-vite/index.html278 B
  • templates/react-vite/package.json394 B
  • templates/react-vite/serve.js3.8 KB
  • templates/react-vite/src/App.jsx767 B
  • templates/react-vite/src/main.jsx213 B
  • templates/react-vite/src/tokens.css6.2 KB
  • templates/react-vite/vite.config.js245 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.