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
Appcomponent withHuitzoProvider(from@huitzo/dashboard-sdk-react) to make SDK hooks available throughout the component tree - Wraps with
ErrorBoundaryto 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}andunmount()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-reactpackage 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()frommain.tsxand calls it with a mock HuitzoContext - Simulates exactly how Hub would load the dashboard, so the developer tests the real
mount/unmountlifecycle locally index.htmlloads this file as the Vite entry point duringnpm run dev- When Vite builds in library mode,
index.htmlis excluded — onlymain.tsxexports 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.DEVbranches 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.tsxandindex.htmldon't exist; Hub callsmount()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_pointfield 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
useCommandhook to execute Intelligence Pack commands - Uses
useHuitzohook 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
ErrorBoundarycatches these internal errors and shows a friendly error state with a "Return to Hub" button viacontext.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:
- Vite serves
index.htmlas the entry point index.htmlloadssrc/dev.tsxdev.tsximportsmount()frommain.tsxand calls it with a mockHuitzoContext- The dashboard renders exactly as it would inside Hub — same
mount/unmountlifecycle
# 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.html → dev.tsx → mount() |
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.htmlin 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:
- Validate — runs the same checks as
huitzo dashboard validate - Package — creates a
.tar.gztarball fromdist/ - Upload — sends the tarball to the Huitzo registry
- 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)
Share Links¶
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.
Related Documentation¶
- CLI Reference — Dashboard Commands — Complete command reference with all options
- CLI Overview — CLI installation and design philosophy
- Dashboard Framework Overview — What dashboards are and how they run inside Hub
- Dashboard Loading Architecture — How Hub discovers, loads, and renders dashboards
- Dashboard Publishing — Build contract, bundle format, validation rules
- Dashboard Manifest Reference — Complete
huitzo-dashboard.yamlspecification - Dashboard SDK Reference —
@huitzo/dashboard-sdkand@huitzo/dashboard-sdk-reactAPI - Deployment Guide — Huitzo Cloud and self-hosted Hub options