Dashboard CLI Guide

Dashboard CLI Guide

This guide walks through the complete dashboard development lifecycle — from scaffolding a new project to publishing it on Huitzo Hub. For the command reference with all options, see CLI Reference — Dashboard Commands.

Overview

Dashboard CLI commands manage micro-frontend applications that run inside Huitzo Hub. Each dashboard is an independent React app that exports mount/unmount functions, gets loaded by Hub on demand, and communicates with Intelligence Packs via the Dashboard SDK.

Developer's Machine                              Huitzo Hub
┌──────────────────────────────────────┐         ┌───────────────────────────┐
│                                      │         │                           │
│  1. new    — scaffold project        │         │  6. Extract tarball       │
│  2. dev    — local dev server        │         │  7. Register in catalog   │
│  3. build  — dist/main.js (ESM)     │         │  8. Serve at /d/{slug}    │
│  4. validate — check manifest+bundle │         │  9. User clicks tile      │
│  5. publish — upload tarball ────────│────────▶│ 10. import() → mount()    │
│                                      │         │                           │
└──────────────────────────────────────┘         └───────────────────────────┘

Prerequisites

Requirement Version Notes
Node.js 25+ Required for Vite and npm
npm 10+ Comes with Node.js
Huitzo CLI Latest uv pip install huitzo-cli
Authentication Required only for publish, grant, revoke, share

Step 1 — Scaffold a New Dashboard

huitzo dashboard new claims-dashboard

This generates a complete, build-ready project. No additional configuration is needed to run npm install && npm run dev.

Template File Structure

claims-dashboard/
├── huitzo-dashboard.yaml       # Dashboard manifest — metadata, pack deps, build config
├── package.json                # npm deps: react 19.2, @huitzo/dashboard-sdk, @huitzo/dashboard-sdk-react
├── vite.config.ts              # Vite library mode — outputs dist/main.js (self-contained ESM)
├── tsconfig.json               # TypeScript strict mode config
├── index.html                  # Dev-only — loads src/dev.tsx for local development
├── src/
│   ├── main.tsx                # PRODUCTION ENTRY — exports mount() and unmount()
│   ├── dev.tsx                 # DEV-ONLY — imports mount(), calls it with mock HuitzoContext
│   ├── App.tsx                 # Root component — the actual dashboard UI
│   ├── App.module.css          # CSS Modules — scoped styles (prevents Hub style bleed)
│   └── components/
│       └── ErrorBoundary.tsx   # Catches React tree errors inside the dashboard
└── .gitignore                  # node_modules/, dist/, .env.local

Every file in this structure serves a specific role in the micro-frontend architecture. The sections below explain each key file in detail.

Key File: src/main.tsx — The Module Contract

This is the production entry point that Hub loads via dynamic import(). It must export exactly two functions: mount(container, context) and unmount(container).

How it works:

  • mount() creates a new React root inside the Hub-provided DOM container
  • Wraps the App component with HuitzoProvider (from @huitzo/dashboard-sdk-react) to make SDK hooks available throughout the component tree
  • Wraps with ErrorBoundary to catch rendering errors within the dashboard's own React tree
  • unmount() cleans up the React root to prevent memory leaks when the user navigates away
  • Hub calls mount() when the user navigates to /d/{slug} and unmount() when they leave

The HuitzoContext object passed by Hub is a vanilla JavaScript object (not React context). It contains:

Property Type Description
apiUrl string Huitzo API base URL
token string JWT for authenticated API calls
slug string This dashboard's slug
sdkVersion string Hub's SDK version (for compatibility checks)
user object { id, email, roles, tenantId }
navigate(path) function Navigate within Hub
navigateToHub() function Return to Hub home
navigateToDashboard(slug) function Open another dashboard
showNotification(msg, type) function Show Hub notification toast
on(event, handler) function Subscribe to Hub events (returns unsubscribe)
emit(event, data) function Emit event to Hub

Note: The @huitzo/dashboard-sdk-react package provides additional navigation hooks beyond the raw context (e.g., navigateToExplore, navigateToSettings). See Dashboard SDK Reference.

// pseudocode — src/main.tsx production entry point

// Module-level state: React root persists across renders
let root = null

export function mount(container, context):
    root = createRoot(container)
    root.render(
        ErrorBoundary wrapping:
            HuitzoProvider with context:
                App component
    )

export function unmount(container):
    root.unmount()
    root = null

Reference: Loading Architecture — Module Contract

Key File: src/dev.tsx — The Dev Harness

This file exists only for local development — it is never included in the production build.

How it works:

  • Imports mount() from main.tsx and calls it with a mock HuitzoContext
  • Simulates exactly how Hub would load the dashboard, so the developer tests the real mount/unmount lifecycle locally
  • index.html loads this file as the Vite entry point during npm run dev
  • When Vite builds in library mode, index.html is excluded — only main.tsx exports matter

The mock context provides:

  • A local API URL pointing to the Vite proxy (/api)
  • A dev token for local testing
  • Mock user info with admin role
  • Stub navigation functions that log to console
  • A no-op event bus for on/emit
// pseudocode — src/dev.tsx (dev-only, never shipped)

import { mount } from './main'

const mockContext = {
    apiUrl: '/api',                           // Vite proxy handles this
    token: 'dev-token',
    slug: 'claims-dashboard',
    user: { id: 'dev', email: 'dev@localhost', roles: ['admin'], tenantId: 'dev' },
    navigate: (path) => console.log('navigate:', path),
    navigateToHub: () => console.log('navigate to hub'),
    showNotification: (msg, type) => console.log('notification:', type, msg),
    on: (event, handler) => () => {},         // No-op event bus
    emit: (event, data) => console.log('emit:', event, data),
}

mount(document.getElementById('root'), mockContext)

Why this pattern:

  • Dashboard code stays Hub-compatible at all times — no conditional import.meta.env.DEV branches in production code
  • Clean separation of concerns — the dev harness is a separate file, not a mode switch inside main.tsx
  • Customizable — developers can modify the mock context to test different user roles, simulate events, or point to a remote API
  • Invisible in production — when running inside Hub, dev.tsx and index.html don't exist; Hub calls mount() directly

Key File: vite.config.ts — Build Configuration

The dashboard build uses Vite library mode to produce an ESM module, not an HTML application. This is the critical difference from a standard Vite project.

// pseudocode — vite.config.ts (library mode for micro-frontend output)

export default defineConfig:
    plugins: [react()]
    build:
        lib:
            entry: 'src/main.tsx'
            formats: ['es']
            fileName: 'main'
        cssCodeSplit: false
    server:
        proxy:
            '/api'  'http://localhost:8080'
Option Value Why
lib.entry src/main.tsx Production entry — must export mount/unmount
lib.formats ['es'] Hub uses dynamic import() — needs ESM
lib.fileName 'main' Convention: output is always dist/main.js
cssCodeSplit false CSS injected into JS module — single file to load
No external Dashboard bundles everything including React for version independence
server.proxy /api → localhost Routes API calls to local backend during dev

No external configuration — all dependencies (React, ReactDOM, Dashboard SDK) are bundled into the output. This ensures dashboards have no version coupling with Hub. See Publishing — Why Dashboards Bundle Everything for the rationale.

Reference: Dashboard Publishing — Vite Library Mode

Key File: package.json — Dependencies

The template includes two categories of dependencies:

Runtime dependencies (bundled into production output):

Package Purpose
react UI framework (v19.2)
react-dom DOM rendering
@huitzo/dashboard-sdk Core SDK — types, API client, error classes
@huitzo/dashboard-sdk-react React bindings — HuitzoProvider, hooks

Dev dependencies (build tooling only):

Package Purpose
vite Build tool and dev server
@vitejs/plugin-react React support for Vite
typescript Type checking
@types/react, @types/react-dom React type definitions

Scripts:

Script Command Description
dev vite Start dev server with HMR
build vite build Build library mode output
preview vite preview Preview production build

Both SDK packages are runtime dependencies because they are bundled into the production output. The dashboard bundles its own React — there is no shared React with Hub.

Key File: huitzo-dashboard.yaml — Manifest

The generated manifest contains the minimum required configuration:

# pseudocode — generated huitzo-dashboard.yaml skeleton

dashboard:
  name: "claims-dashboard"        # From CLI argument (kebab-case)
  namespace: "claims"             # Derived from name
  version: "0.1.0"               # Initial version
  description: "A Huitzo Dashboard"
  visibility: "private"           # Scaffold default (system default is "organization")

pack_dependencies: []             # Add your pack deps here

build:
  framework: "react"
  build_command: "npm run build"
  output_directory: "dist"
  entry_point: "main.js"          # Vite library mode output: dist/main.js

Note: The entry_point field is set to "main.js" to match the Vite library mode build convention (dist/main.js). This is the correct value for dashboards using the micro-frontend module contract. See Dashboard Publishing — Build Contract.

Section Description
dashboard Core metadata — name, version, visibility
pack_dependencies Intelligence Packs this dashboard consumes (empty by default)
build Build configuration — framework, commands, output paths

Reference: Dashboard Manifest Reference for all fields including pricing, deployment, and metadata sections.

Key File: App.tsx — Root Component

The actual dashboard UI lives here. The template includes a minimal example that demonstrates SDK usage:

  • Uses useCommand hook to execute Intelligence Pack commands
  • Uses useHuitzo hook to access the SDK client and user context
  • Uses CSS Modules (App.module.css) to scope styles — prevents collisions with Hub and other dashboards
// pseudocode — src/App.tsx (minimal template)

import styles from './App.module.css'
import { useCommand } from '@huitzo/dashboard-sdk-react'

function App():
    const { execute, data, loading, error } = useCommand('@scope/pack/command')

    if loading: return <Spinner />
    if error: return <ErrorDisplay error={error} />

    return (
        <div className={styles.container}>
            dashboard UI using data from pack commands
        </div>
    )

CSS Modules are required — global CSS risks style bleed into Hub or other dashboards. See Loading Architecture — CSS Scoping.

Key File: ErrorBoundary.tsx

The template includes a lightweight React Error Boundary that catches rendering errors within the dashboard's component tree.

Why this is needed:

  • Hub wraps each dashboard mount point in its own error handling, but that catches mount()/unmount()-level failures only
  • Since the dashboard creates its own React root via createRoot, errors inside the dashboard's React tree do not propagate to Hub's boundary
  • The template's ErrorBoundary catches these internal errors and shows a friendly error state with a "Return to Hub" button via context.navigateToHub()
Error Type Caught By
Module fails to load (import() rejects) Hub
mount() throws Hub
Runtime error inside React tree Dashboard's ErrorBoundary
unmount() throws Hub (best-effort)

See Loading Architecture — Error Handling for the full error handling model.

Template Options

huitzo dashboard new [NAME] [OPTIONS]
Option Default Description
--template basic Template variant
--namespace (derived from name) Dashboard namespace
--author (from git config) Author name

Template variants:

Template Description
basic Minimal setup — App.tsx with a counter, CSS Modules, no external styling framework
tailwind Adds Tailwind CSS configuration on top of the basic template
full Adds routing, multiple pages, example pack integration, comprehensive error handling

Step 2 — Develop Locally

# pseudocode — start local development
cd claims-dashboard
npm install
huitzo dashboard dev

This wraps npm run dev — starts a Vite dev server with hot module replacement (HMR).

How dev mode works:

  1. Vite serves index.html as the entry point
  2. index.html loads src/dev.tsx
  3. dev.tsx imports mount() from main.tsx and calls it with a mock HuitzoContext
  4. The dashboard renders exactly as it would inside Hub — same mount/unmount lifecycle
# pseudocode — example dev server output

Dashboard development server started

   Local:    http://localhost:3000
   Network:  http://192.168.1.100:3000

   API Proxy: http://localhost:8080 → /api/*

   Press Ctrl+C to stop

Dev Mode vs Hub Mode

Aspect Dev Mode (huitzo dashboard dev) Hub Mode (production)
Entry mechanism index.htmldev.tsxmount() Hub calls import()mount() directly
Authentication Mock token in dev.tsx Real JWT via Hub session
Navigation Console logging stubs Hub-controlled URL routing
API access Vite proxy to local/remote backend Direct API calls with JWT
HMR Yes — instant feedback No — publish new version

API proxy configuration: The Vite dev server proxies /api requests to the local backend (default http://localhost:8080). To connect to a remote backend, update the proxy target in vite.config.ts or use --api-url.


Step 3 — Build for Production

huitzo dashboard build

This wraps npm run build with Vite in library mode. The output is a self-contained ESM module — not an HTML application.

# pseudocode — example build output

Building claims-dashboard for production...

Build complete!

   Output: ./dist
   Size:   245 KB (gzipped: 72 KB)

   Files:
   - dist/main.js    (self-contained ESM — React + SDK + app + CSS)

Ready to validate with 'huitzo dashboard validate'

Key points:

  • Output is dist/main.js — a single self-contained ESM module containing React, SDK, application code, and CSS
  • No index.html in build output — library mode does not produce one
  • CSS is injected into the JS module via <style> tags on import — no separate CSS file
  • Optional dist/assets/ directory for static files (images, fonts) referenced by the dashboard
  • All dependencies are bundled — the output has zero external imports

See Dashboard Publishing — Build Contract for the full specification.


Step 4 — Validate

huitzo dashboard validate

Validates the manifest and build output without publishing. Run this before publish to catch issues early.

# pseudocode — example validation output

Validating claims-dashboard...

  Manifest checks:
  ✓ huitzo-dashboard.yaml found
  ✓ dashboard.name: "claims-dashboard" (valid kebab-case)
  ✓ dashboard.version: "1.2.0" (valid semver)
  ✓ dashboard.namespace: "claims" (valid)
  ✓ dashboard.description: present (10-200 chars)
  ✓ pack_dependencies: 2 declared, all valid

  Bundle checks:
  ✓ dist/main.js found (entry point)
  ✓ Bundle is valid ESM
  ✓ Exports: mount, unmount (valid module contract)
  ✓ Bundle size: 1.2 MB (under 50 MB limit)
  ✓ File count: 3 (under 500 limit)
  ✓ All file types allowed

All checks passed.

Validation Checks

Manifest validation:

Check Rule
Required fields name, namespace, version, description present
Name format kebab-case, 3-50 characters, ^[a-z][a-z0-9-]*[a-z0-9]$
Namespace format Lowercase, no dashes, 2-20 characters
Version format Valid semver (MAJOR.MINOR.PATCH)
Description length 10-200 characters
Visibility value One of: public, unlisted, organization, private
min_sdk_version Valid semver, must be a released SDK version

Bundle validation:

Check Rule
Entry point exists dist/main.js must exist
Entry point is ESM File is a valid ES module
Exports mount Named export mount present
Exports unmount Named export unmount present
Size limit Tarball ≤ 50 MB
File count ≤ 500 files
Allowed file types .js, .css, .json, .png, .jpg, .svg, .woff, .woff2, .ttf

Reference: Dashboard Publishing — Validation Rules is the canonical source for validation rules.


Step 5 — Publish

Cloud-gated: Requires Huitzo Cloud authentication (huitzo login).

huitzo dashboard publish

Publishing follows this pipeline:

  1. Validate — runs the same checks as huitzo dashboard validate
  2. Package — creates a .tar.gz tarball from dist/
  3. Upload — sends the tarball to the Huitzo registry
  4. Register — the registry extracts, stores, and makes the dashboard available at /d/{slug}
# pseudocode — example publish output

Publishing [email protected]...

  Authenticating with Hub
  Validating manifest and bundle...  passed
  Packaging dist/ → claims-dashboard-1.2.0.tar.gz (148 KB)
  Uploading...

  Published successfully!

  Hub URL:     https://hub.huitzo.com/d/claims-dashboard
  Explore:     https://hub.huitzo.com/explore/dashboards/claims-dashboard
  Visibility:  organization

Options:

Option Default Description
--visibility (from manifest) Override visibility level
--dry-run false Simulate without publishing

Version management: The latest publish becomes the active version immediately — there is no gradual rollout. To roll back, publish the previous code with a new patch version.

Reference: Dashboard Publishing — Version Management


Step 6 — Access Control

All access control commands require authentication.

Grant Access

Grant a tenant access to an organization-scoped dashboard. Currently accepts tenant UUIDs directly — organization-name-to-UUID resolution will be available when the backend adds GET /api/v1/organizations?slug=<name>.

huitzo dashboard grant <DASHBOARD> <TENANT_UUID>
# pseudocode — example grant output

huitzo dashboard grant claims-dashboard 550e8400-e29b-41d4-a716-446655440000

  Looking up dashboard 'claims-dashboard'...
  Granting tenant 550e8400-e29b-41d4-a716-446655440000 access...
  Granted access (1 tenant(s))

Revoke Access

Revoke a tenant's access to a dashboard.

Note: Backend revoke endpoint (DELETE /api/v1/dashboards/{id}/grant) is not yet implemented.

huitzo dashboard revoke <DASHBOARD> <TENANT_UUID>
# pseudocode — example revoke output (target behavior)

huitzo dashboard revoke claims-dashboard 550e8400-e29b-41d4-a716-446655440000

  Revoked tenant access to claims-dashboard

  Current access:
  - @acme (owner)

Generate a shareable link for unlisted dashboards:

huitzo dashboard share <DASHBOARD> [--expires <DURATION>]
# pseudocode — example share output

huitzo dashboard share claims-dashboard --expires 7d

  Shareable link for claims-dashboard:

     https://hub.huitzo.com/d/abc123xyz

     Visibility: unlisted
     Expires: 2026-03-29

  Anyone with this link can view and install the dashboard.
Option Default Description
--expires never Link expiration (1d, 7d, 30d, never)

End-to-End Example

A complete session from scaffold through publish:

# pseudocode — full dashboard lifecycle

# 1. Scaffold
huitzo dashboard new claims-dashboard
cd claims-dashboard

# 2. Install dependencies
npm install

# 3. Edit huitzo-dashboard.yaml to add pack dependencies
#    Add @acme/claims-processor as required dependency

# 4. Develop locally
huitzo dashboard dev
# → http://localhost:3000 — edit src/App.tsx, see changes instantly

# 5. Build for production
huitzo dashboard build
# → dist/main.js (self-contained ESM)

# 6. Validate before publishing
huitzo dashboard validate
# → All checks passed

# 7. Authenticate (first time only)
huitzo login

# 8. Publish
huitzo dashboard publish
# → https://hub.huitzo.com/d/claims-dashboard

# 9. Grant access to a partner tenant (by UUID)
huitzo dashboard grant claims-dashboard <tenant-uuid>

How Dashboards Reach Users

After publishing, dashboards become available through Hub:

Developer publishes                Hub Home
─────────────────                  ──────────
huitzo dashboard publish    →      Dashboard appears in grid
                                        │
                                   User clicks tile
                                        │
                                        ▼
                                   Route activation: /d/claims-dashboard
                                        │
                                        ▼
                                   Hub fetches dashboard config from API
                                        │
                                        ▼
                                   Dynamic import(entryPointUrl)
                                   Service worker injects JWT auth header
                                        │
                                        ▼
                                   Browser loads dist/main.js
                                        │
                                        ▼
                                   Hub calls module.mount(container, huitzoContext)
                                        │
                                        ▼
                                   Dashboard creates React root and renders
                                        │
                                        ▼
                                   User interacts with dashboard
                                   (executes pack commands via SDK)

See Dashboard Loading Architecture for the complete runtime specification.


Command Implementation Status

Command Status Notes
huitzo dashboard new Implemented Scaffolds React + TypeScript project
huitzo dashboard dev Implemented Wraps npm run dev
huitzo dashboard build Implemented Wraps npm run build
huitzo dashboard validate Implemented Checks manifest fields and bundle output (mount/unmount exports, size limit)
huitzo dashboard publish Implemented Validates, packages tarball, registers dashboard, and uploads to backend
huitzo dashboard grant Implemented Grants tenant access via UUID (org-slug-to-UUID resolution pending backend endpoint)
huitzo dashboard revoke Pending backend Backend endpoint DELETE /api/v1/dashboards/{id}/grant not yet implemented
huitzo dashboard share Pending backend Backend endpoint for shareable links not yet implemented

Troubleshooting

npm not found

Node.js is not installed or not on your PATH. Install Node.js 25+ from nodejs.org.

node --version    # Should show v25.x+
npm --version     # Should show 10.x+

Build produces index.html instead of dist/main.js

Your vite.config.ts is missing library mode configuration. The build.lib section must be present with entry: 'src/main.tsx' and formats: ['es']. See Step 1 — vite.config.ts.

Dashboard works in dev but blank in Hub

The most common cause is missing mount/unmount exports from src/main.tsx. In dev mode, dev.tsx calls mount() directly, so the dashboard renders. In Hub, import() loads the module and looks for the mount export — if it's missing, nothing renders.

Verify your main.tsx has:

// pseudocode — required exports
export function mount(container, context) { ... }
export function unmount(container) { ... }

Validation fails: "Bundle does not export mount"

The build output (dist/main.js) is not exporting mount as a named export. This usually means: - vite.config.ts is not using library mode (missing build.lib) - main.tsx uses export default instead of named exports - The entry point in build.lib.entry doesn't match your actual file

CSS not applied after build

If styles work in dev but disappear in the production build, check that cssCodeSplit is set to false in vite.config.ts. Without this setting, Vite may produce a separate CSS file that Hub won't load.

Dev mode shows errors about missing HuitzoContext properties

The mock context in src/dev.tsx is out of date. Hub's HuitzoContext may have new properties that your mock doesn't provide. Update the mock object in dev.tsx to match the HuitzoContext interface.