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.
npx gooseworks install --all # then, in Claude Code, Cursor, or Codex: /gooseworks use the create-dashboard 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.
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:
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./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.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./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.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.node_modules and dist are LOCAL only. Never copy them into the workspace folder.Before editing, inspect:
package.json is a symlink (it should be — this confirms the symlink layout is in place)node_modules folder inside the runnable project folder is populatedThen follow this decision flow:
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.
Before coding:
Order of preference for every panel, chart, and table:
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:
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.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.
Template structure to use:
For each new page:
Keep all dashboard endpoints read-only.
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:
| Shell | Use when the user asks for… |
|---|---|
SidebarLayout (default) | a multi-section app with several pages (analytics, admin, multi-page tool) |
TopNavLayout | a single-purpose dashboard, marketing-style report, or something that wants full-width content |
TopNavTabsLayout | a Stripe-Dashboard-style sectioned view where tabs slice the same workspace |
SplitPaneLayout | inbox / CRM / chat / mail-style apps — list on the left, detail on the right. Pass list and detail as separate props |
CanvasLayout | a one-page report, embed, or screen with no chrome at all |
CenteredLayout | login forms, onboarding screens, single-action surfaces |
Rules:
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.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:
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.When in doubt, look at the existing pages in the template and match their density, spacing, and tone before adding anything new.
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:
npm run build from /home/user/dashboard.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.
Only restart the server when one of these is true:
server.js itself (new API route, new middleware, anything
that changes server behavior — Express won't pick those up without a
process restart)./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.
Hit the health endpoint:
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.
/api/health endpoint). If not, ask the platform to re-run the start flow.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.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.dist folder inside the runnable project folder and rebuild.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.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.
End with a direct status message that the dashboard is live in the App tab and ready for further edits.
For every follow-up tweak (this loop is the agent's job, not the user's):
/home/user/dashboard/.... Symlinks persist edits to the workspace automatically; no sync step.npm run build from /home/user/dashboard. Do not restart the server — Express serves the new dist/ automatically on the next request.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).
cd /home/user/dashboard before running npm / vite / node //home/user/dashboard/src/... (or anynpm install, npm ci, vite build, or node server.jspackage.json is a symlink (it should be — this confirms the symlink layout is in place)node_modules folder inside the runnable project folder is populatedAssemble 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.
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.
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.