contentdesign

Create Dashboard

Create a custom web dashboard (React + Vite + Express) inside your sandbox to visualize the agent's Turso database. The dashboard is served on port 3847 and the user sees it live in the "App" tab in Gooseworks. Use when the user asks for a dashboard, visualization, chart, metric view, or any custom UI powered by their agent's data.

Gooseby Athina AI
Install
Terminal
npx gooseworks install --all

# then, in Claude Code, Cursor, or Codex:
/gooseworks use the create-dashboard skill
About This Skill

You are helping the user build a custom dashboard from the Gooseworks dashboard template. The app must run on port 3847 from a single Express process that serves both the API routes and the built React UI so it appears in the Gooseworks App tab.

Where the source lives (read this first)

The runnable project folder is /home/user/dashboard. That is the ONLY directory you should cd into for any npm / build / server command. Most files inside it are symlinks pointing back into the canonical source under the agent's workspace folder (the file you'd see at the canonical path is the same file you'd see through the symlink — it's one file, two paths). The runnable project folder also holds two real local directories that must NOT be on the workspace mount: node_modules (dependencies) and dist (built bundle).

/home/user/dashboard/                    ← cd here for everything
  package.json, package-lock.json,
  server.js, src/, vite.config.ts, …    (symlinks → workspace canonical source)
  node_modules/, dist/, .vite/, .cache/  (real local dirs — never on workspace)

What this means for you:

  • Always cd /home/user/dashboard before running npm / vite / node / any shell command. Tools resolve modules from the node_modules next to the cwd. Running them from the canonical source path under the workspace folder will install node_modules directly into the workspace mount — that puts tens of thousands of files on s3fs, hits its filesystem-semantics limits (npm gets ENOTEMPTY on package renames), and the install will spin forever.
  • Edits to source files at /home/user/dashboard/src/... (or any other symlinked path) auto-persist to the workspace mount through the symlink. There is no separate sync step. You may also edit the canonical path directly; both paths land in the same file.
  • Never run npm install, npm ci, vite build, or node server.js from inside the workspace canonical source folder. Those commands will pollute the workspace with node_modules / dist / build caches and break future restores.

Non-negotiable constraints

  1. Always use the template workflow (React + Vite + Tailwind + Express). Do not rebuild this in another framework.
  2. The runnable project folder is /home/user/dashboard. Use that literal path when telling the user where you cd or which file you edited; do not invent shell-variable strings.
  3. Always cd /home/user/dashboard before running npm / vite / node. Edits to symlinked source files inside it propagate to persistent storage automatically; no separate sync step is needed.
  4. node_modules and dist are LOCAL only. Never copy them into the workspace folder.
  5. Use one runtime port (3847) and one server process. No separate frontend dev server.

State handling

Before editing, inspect:

  • whether the runnable project folder's package.json is a symlink (it should be — this confirms the symlink layout is in place)
  • whether the local node_modules folder inside the runnable project folder is populated
  • whether port 3847 has a healthy server

Then follow this decision flow:

  • All three OK: move to customization.
  • Symlinks missing or node_modules empty: ask the platform to re-run the start flow (it will set up symlinks + npm install + build + launch).
  • package.json is a real file (not a symlink): the sandbox is in a legacy state — ask the platform to re-run install/start so the symlink layout gets put in place.

The platform's start/install orchestrator handles symlink setup, dependency install, build, and launch automatically. You don't run those steps by hand unless something is broken.

Discovery and planning

Before coding:

  1. Clarify what the user wants to visualize if unclear.
  2. Always check the agent's database first. If the agent has a Turso database with relevant tables, the dashboard must read from it. Never invent placeholder rows, mock arrays, or hard-coded sample data when real data is available. Use mock data only when the user explicitly asks for a demo with no DB, and clearly label it as mock in the UI.
  3. Inspect the database schema using the database query tool:
    • list tables
    • inspect columns for relevant tables
    • run a small sample query to confirm shape and row counts before wiring a chart
    • if the relevant tables are missing, follow the Empty-database handling section below — propose a schema and create the tables rather than defaulting to mock data
  4. If the request is vague, confirm a one-sentence implementation plan.

Data source preference

Order of preference for every panel, chart, and table:

  1. Live query against the agent's database via the runQuery helper or a read-only API route.
  2. A user-provided file (CSV, JSON) already in the workspace.
  3. Mock data — only as a last resort, with explicit user permission, and labelled as such on screen.

Empty-database handling (important)

The user is often non-technical and will not know how to create a schema themselves. Do not fall back to "sample data" the moment a table is missing. Instead, when the DB is reachable but the tables needed for the requested dashboard do not exist:

  1. Confirm which tables exist by listing them through the database tool.
  2. Tell the user in plain language that the table(s) the dashboard needs are not there yet, and propose a small, sensible schema based on what they asked for (e.g., for a "revenue dashboard": a deals table with id, name, amount, stage, closed_at, plus a revenue_daily rollup if useful). Keep the schema minimal — only the columns the requested charts actually need.
  3. Get a one-line confirmation from the user, then create the tables via the database tool. Use sensible types and primary keys. Add helpful indexes for the columns the dashboard will filter or group by.
  4. Offer to seed a small set of realistic example rows so the charts have something to render immediately. Seed only if the user agrees, and tell them clearly that these rows are starter examples they can delete or replace.
  5. Wire the dashboard to the newly created tables. Do not also keep a mock-data fallback in the code — once the table exists, the empty state in the UI is enough.

If the user declines schema creation, render a calm empty state ("no data yet — connect a table named X with columns A, B, C to see this chart") in stone tones rather than filling the chart with fake numbers.

Never create or alter tables that already contain user data without an explicit instruction. Never drop tables. All table creation must be additive.

Implementation guidance

Template structure to use:

  • A server entry module for API routes and static serving.
  • The root App component for route registration.
  • A layouts folder under src/components/layouts/ that holds six shell components — pick the one that matches what the user is building (see "Choosing a layout" below).
  • A pages folder for page implementations.
  • A small API helper module exporting a runQuery function for data access from pages.

For each new page:

  1. Add a page component to the pages folder.
  2. Pull data using the runQuery helper, or a dedicated read-only API route when SQL becomes complex.
  3. Add route wiring in the root App component.
  4. Add a navigation entry in whichever layout shell the App component is wrapped in (sidebar nav, top nav, or tab bar — depending on the chosen layout).

Keep all dashboard endpoints read-only.

Choosing a layout

Six layout shells live in src/components/layouts/. The default App.tsx wraps routes in SidebarLayout. Swap the import + wrapper in App.tsx to the shell that matches what the user is asking for:

ShellUse when the user asks for…
SidebarLayout (default)a multi-section app with several pages (analytics, admin, multi-page tool)
TopNavLayouta single-purpose dashboard, marketing-style report, or something that wants full-width content
TopNavTabsLayouta Stripe-Dashboard-style sectioned view where tabs slice the same workspace
SplitPaneLayoutinbox / CRM / chat / mail-style apps — list on the left, detail on the right. Pass list and detail as separate props
CanvasLayouta one-page report, embed, or screen with no chrome at all
CenteredLayoutlogin forms, onboarding screens, single-action surfaces

Rules:

  • Pick exactly one shell per dashboard. Do not mix two shells in App.tsx.
  • Before swapping the shell, confirm the choice with the user in one sentence ("I'll build this as a split-pane inbox — list on the left, message on the right. OK?").
  • After swapping the shell, edit the nav entries inside that shell file to match the routes you wire up.
  • All shells share the same stone palette, typography, and spacing — do not introduce new colors or fonts when switching shells.
  • SplitPaneLayout takes list + detail props instead of children. App.tsx should render the list pane (route-agnostic) and the detail pane (typically a <Routes>) as those two props.

Visual style rules

Design inspiration: aim for the calm, content-first feel of Linear, Vercel, Stripe, and Notion analytics dashboards. The dashboard should look like a quiet reporting surface, not a colourful BI tool. Density is good; chart-junk is not.

Concrete rules:

  • Use the stone color palette only — stone-tinted text, borders, and backgrounds. No blues, greens, purples, or rainbow palettes.
  • Prefer compact typography (extra-small or small text sizes) with a normal font weight. Avoid bold headlines except for the page title.
  • Keep borders minimal (single hairline in a light stone tone) and avoid heavy shadows or rounded "card" stacks. Flat surfaces only.
  • For Recharts lines, bars, and grids, stay within the stone color family. Use opacity to differentiate series instead of hue. Single-series charts should be a single mid-stone tone.
  • Layout: pick a shell from src/components/layouts/ (see "Choosing a layout" above) instead of inventing a new chrome. Within whichever shell you pick, keep the main content column generous on whitespace, KPIs as a row of small stat blocks at the top, charts and tables stacked below.
  • Empty states, loading states, and error states must follow the same stone palette and typography — no spinners with brand colours, no red error toasts.

When in doubt, look at the existing pages in the template and match their density, spacing, and tone before adding anything new.

Build, run, and verify

The dashboard does not hot-reload on file changes. After editing source files under /home/user/dashboard/src/..., run npm run build from /home/user/dashboard. You do not need to restart the server for src/ edits — the running Express server uses express.static(dist/public) and reads files from disk on every request, so the next iframe refresh picks up the freshly-built bundle automatically (Vite emits new hashed asset filenames; the new index.html points to them).

Default flow after a src/ edit:

  1. Run npm run build from /home/user/dashboard.
  2. Tell the user the update is live and to refresh the App tab. Done.

Do not stop, kill, or restart the dashboard server as part of the normal edit loop. Doing so wastes time and creates a failure mode: the agent backgrounds node server.js, then polls the background task waiting for it to "complete" — but a healthy server is a long-lived process, so the poll loop never resolves and the chat appears frozen.

When a restart IS warranted

Only restart the server when one of these is true:

  • You edited server.js itself (new API route, new middleware, anything that changes server behavior — Express won't pick those up without a process restart).
  • The server isn't running on 3847. The platform's start flow is the right tool for this — ask it to start; do not hand-roll a launch.
  • The user reports a visible problem with the dashboard (blank App tab, stale UI even after refresh, an error they can see) and a restart is a plausible fix. In that case: stop the old process using the port-scoped cleanup pattern (never broad pkill inside the sandbox), launch the new one, then verify health via the /api/health endpoint.

If you do launch node server.js yourself, fire-and-forget it (redirect output to a log file, return immediately) and verify via the /api/health endpoint. Never poll a background-task output waiting for the server process to exit — it won't.

Verifying after work that warranted a restart

Hit the health endpoint:

  • ok=true with db=true: dashboard is live.
  • db=false: inform the user that agent DB credentials/config are missing and stop further DB-dependent work.

Troubleshooting

If the user reports a problem with the dashboard, walk through this list in order before making code changes. Most "the dashboard is broken" reports are environmental, not bugs in the user's pages.

  1. App tab is blank or shows a connection error. Check whether the server is actually running on port 3847 (hit the /api/health endpoint). If not, ask the platform to re-run the start flow.
  2. Health endpoint returns ok=false or db=false. The agent's database credentials are missing or invalid. Tell the user this directly; do not try to silently swap in mock data.
  3. Page renders but charts/tables are empty. Run the underlying query through the database tool to confirm whether the table actually has rows. If the table is missing entirely, follow the Empty-database handling section — offer to create the schema; do not silently swap to mock data. If the table exists but is empty, render an empty state. If rows exist, check the runQuery call and column names.
  4. "Module not found" or import errors after editing. A new package was used without being added to package.json. Add it. Then change directory into /home/user/dashboard. Then run npm install. Then rebuild. Never run npm install from anywhere under the workspace folder — that installs node_modules into the workspace mount, which hits s3fs filesystem-semantics limits and spins forever.
  5. Changes do not appear in the App tab. The build wasn't run after the edit, or the user hasn't refreshed yet. Run npm run build from /home/user/dashboard and ask the user to refresh — Express serves the new bundle from disk, so no server restart is needed for src/ edits. Only restart the server if a refresh still shows stale output.
  6. Stale UI after a long session. The build output diverged from source. Remove the dist folder inside the runnable project folder and rebuild.
  7. The runnable project folder's package.json is a real file (not a symlink). The sandbox is in a legacy / pre-symlink state. Ask the platform to re-run the start flow — it will rewire the symlinks and rebuild.
  8. Sandbox restarted and the dashboard isn't running. Just ask the platform to start it. The source persists in S3 via symlinks, so there's nothing for you to restore.

If none of the above resolves it, read the server logs, summarize the actual error to the user in plain language, and propose the smallest fix.

Completion message

End with a direct status message that the dashboard is live in the App tab and ready for further edits.

Iteration loop

For every follow-up tweak (this loop is the agent's job, not the user's):

  1. Edit relevant files at /home/user/dashboard/.... Symlinks persist edits to the workspace automatically; no sync step.
  2. Run npm run build from /home/user/dashboard. Do not restart the server — Express serves the new dist/ automatically on the next request.
  3. Tell the user the update is done and ask them to refresh the App tab.

Restart the server only in the cases listed under "When a restart IS warranted" above (server.js changed, server not running, or the user reports a visible problem).

What's included

·
*Always cd /home/user/dashboard before running npm / vite / node /
·
*Edits to source files at /home/user/dashboard/src/... (or any
·
*Never run npm install, npm ci, vite build, or node server.js
·
whether the runnable project folder's package.json is a symlink (it should be — this confirms the symlink layout is in place)
·
whether the local node_modules folder inside the runnable project folder is populated
You Might Also Like

Render VO Anchored Motion Listicle

Assemble an expert/educator motion-graphic LISTICLE video ad from a config — a spoken authoritative voiceover carries a numbered listicle while N web-animated hyperframe beats (HTML plus the Web Animations API, one branded design system of alternating tiles, big hero numerals, and glass-pill callouts) are rendered frame-by-frame via Playwright and anchored to the VO's word-level timestamps, periodic color-graded B-roll windows give visual breath, and captions burn ONLY inside those B-roll windows (2-word chunks, ASS Format header carrying a Name field so none drop) with the VO mixed under a low music bed. This is the FREE deterministic assembly stage (Playwright beat render plus ffmpeg concat plus window-masked caption burn plus VO-and-music mix plus final composite) — the VO, the music bed, and the stock B-roll come from create-vo-elevenlabs, create-music-elevenlabs, and media-proxy. Use for the vo-anchored-motion-listicle format.

Render Stopmotion Hand Swatch Cycle

Assemble a stop-motion hand-swatch-cycle product-demo ad from a config — a sequence of still PLATES (one hand swiping a single-barrel cosmetic across a cream skin-patch, the barrel + swatch changing per plate while the hand, background, crop, and lighting stay locked) is PNG→mp4 loop-encoded at each plate's own stop-motion hold (fast motion frames 150–250ms, per-shade ~380ms, hero beats 1100–1800ms), concat-demuxed with HARD cuts into a silent master, closed on a Playwright HTML-rendered branded end card (serif tagline + sans subtitle + real logo SVG over a hero BG, never AI-rendered text), and muxed with a pre-sourced music track playing under the end card with a fade tail (no VO). This is the FREE deterministic assembly stage (loop-encode + concat-demux + end-card render + music mux); the master-anchor plate, shade plates, and end-card BG come from create-image-gpt-image-fal and the track from create-music-elevenlabs. Use for the stopmotion-hand-swatch-cycle format.

Render Split Screen Creator

Assemble a split-screen creator ad from a config — a two-zone vertical composite where a supplied AI-creator lip-sync take fills the BOTTOM ~48% while real 16:9 product/demo clips run uncropped in the TOP ~52%, each top clip contain-fit with a darkened blurred cover-scale fill of the same clip (never black bars), a 3px brand-color divider between the zones, the creator slice cover-fit per the per-scene VO timing, scenes hard-concatenated with the body audio being the concatenated creator VO slices, an end card held on the last sharp frame ~3s, then the ASSEMBLED cut transcribed with local Whisper (not the raw VO — concat drops inter-scene silence) and word-level captions burned in the chosen style. This is the FREE deterministic assembly + caption stage (two-zone composite + blurred fill + divider + hard-concat + end card + captions); the VO comes from create-vo-elevenlabs, the anchor from create-image-gpt-image-fal, and the whole-VO lip-sync from a paid VEED Fabric 1.0 take (a no-atom upstream input). Use for the split-screen-creator format.

Newsletter

Learn to build Growth systems with AI

2-3 compounding systems per week using Claude Code, OpenClaw, and more.