Create dashboard

Create a custom web dashboard (React + Vite + Express) inside your sandbox to visualize the agent's Turso database.

How to use it

  1. Hit Copy SKILL.md — or use the Claude Code line below to get every file.
  2. Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
    ChatGPT: make a Project and paste it into Instructions.
    Neither? Paste it at the top of a new chat — it works for that chat.
  3. Describe your job in plain words. The AI follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit gooseworks-ai/goose-skills/skills/design/capabilities/create-dashboard#main ~/.claude/skills/create-dashboard

For one project only, change the path to .claude/skills/create-dashboard. This skill also uses package.json, package-lock.json, server.js — copying SKILL.md alone won't be enough. See the folder on GitHub.

Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Show the full text236 lines
create-dashboard/SKILL.md236 lines16.0 KBpushed 96d agoRawView on GitHub

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:

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:

  • 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).

1---
2name: create-dashboard
3description: >
4 Create a custom web dashboard (React + Vite + Express) inside your sandbox to visualize
5 the agent's Turso database. The dashboard is served on port 3847 and the user sees it
6 live in the "App" tab in Gooseworks. Use when the user asks for a dashboard,
7 visualization, chart, metric view, or any custom UI powered by their agent's data.
8tags: [content, design]
9---
10 
11You are helping the user build a custom dashboard from the Gooseworks dashboard template.
12The app must run on port 3847 from a single Express process that serves both the API
13routes and the built React UI so it appears in the Gooseworks App tab.
14 
15## Where the source lives (read this first)
16 
17The **runnable project folder is `/home/user/dashboard`**. That is the
18ONLY directory you should `cd` into for any npm / build / server
19command. Most files inside it are symlinks pointing back into the
20canonical source under the agent's workspace folder (the file you'd
21see at the canonical path is the same file you'd see through the
22symlink — it's one file, two paths). The runnable project folder also
23holds two real local directories that must NOT be on the workspace
24mount: `node_modules` (dependencies) and `dist` (built bundle).
25 
26```
27/home/user/dashboard/ ← cd here for everything
28 package.json, package-lock.json,
29 server.js, src/, vite.config.ts, … (symlinks → workspace canonical source)
30 node_modules/, dist/, .vite/, .cache/ (real local dirs — never on workspace)
31```
32 
33What this means for you:
34 
35- **Always `cd /home/user/dashboard` before running npm / vite / node /
36 any shell command.** Tools resolve modules from the `node_modules`
37 next to the cwd. Running them from the canonical source path under
38 the workspace folder will install `node_modules` directly into the
39 workspace mount — that puts tens of thousands of files on s3fs,
40 hits its filesystem-semantics limits (npm gets `ENOTEMPTY` on
41 package renames), and the install will spin forever.
42- **Edits to source files at `/home/user/dashboard/src/...` (or any
43 other symlinked path) auto-persist** to the workspace mount through
44 the symlink. There is no separate sync step. You may also edit the
45 canonical path directly; both paths land in the same file.
46- **Never run `npm install`, `npm ci`, `vite build`, or `node server.js`
47 from inside the workspace canonical source folder.** Those commands
48 will pollute the workspace with `node_modules` / `dist` / build
49 caches and break future restores.
50 
51## Non-negotiable constraints
52 
531. Always use the template workflow (React + Vite + Tailwind + Express). Do not rebuild this in another framework.
542. 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.
553. 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.
564. `node_modules` and `dist` are LOCAL only. Never copy them into the workspace folder.
575. Use one runtime port (3847) and one server process. No separate frontend dev server.
58 
59## State handling
60 
61Before editing, inspect:
62- whether the runnable project folder's `package.json` is a symlink (it should be — this confirms the symlink layout is in place)
63- whether the local `node_modules` folder inside the runnable project folder is populated
64- whether port 3847 has a healthy server
65 
66Then follow this decision flow:
67- All three OK: move to customization.
68- Symlinks missing or `node_modules` empty: ask the platform to re-run the start flow (it will set up symlinks + npm install + build + launch).
69- `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.
70 
71The platform's start/install orchestrator handles symlink setup, dependency
72install, build, and launch automatically. You don't run those steps by
73hand unless something is broken.
74 
75## Discovery and planning
76 
77Before coding:
781. Clarify what the user wants to visualize if unclear.
792. **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.
803. Inspect the database schema using the database query tool:
81 - list tables
82 - inspect columns for relevant tables
83 - run a small sample query to confirm shape and row counts before wiring a chart
84 - 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
854. If the request is vague, confirm a one-sentence implementation plan.
86 
87## Data source preference
88 
89Order of preference for every panel, chart, and table:
901. Live query against the agent's database via the runQuery helper or a read-only API route.
912. A user-provided file (CSV, JSON) already in the workspace.
923. Mock data — only as a last resort, with explicit user permission, and labelled as such on screen.
93 
94## Empty-database handling (important)
95 
96The 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:
97 
981. Confirm which tables exist by listing them through the database tool.
992. 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.
1003. 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.
1014. 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.
1025. 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.
103 
104If 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.
105 
106Never create or alter tables that already contain user data without an explicit instruction. Never drop tables. All table creation must be additive.
107 
108## Implementation guidance
109 
110Template structure to use:
111- A server entry module for API routes and static serving.
112- The root App component for route registration.
113- 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).
114- A pages folder for page implementations.
115- A small API helper module exporting a runQuery function for data access from pages.
116 
117For each new page:
1181. Add a page component to the pages folder.
1192. Pull data using the runQuery helper, or a dedicated read-only API route when SQL becomes complex.
1203. Add route wiring in the root App component.
1214. 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).
122 
123Keep all dashboard endpoints read-only.
124 
125### Choosing a layout
126 
127Six layout shells live in `src/components/layouts/`. The default `App.tsx`
128wraps routes in `SidebarLayout`. Swap the import + wrapper in `App.tsx` to
129the shell that matches what the user is asking for:
130 
131| Shell | Use when the user asks for… |
132| --- | --- |
133| `SidebarLayout` (default) | a multi-section app with several pages (analytics, admin, multi-page tool) |
134| `TopNavLayout` | a single-purpose dashboard, marketing-style report, or something that wants full-width content |
135| `TopNavTabsLayout` | a Stripe-Dashboard-style sectioned view where tabs slice the same workspace |
136| `SplitPaneLayout` | inbox / CRM / chat / mail-style apps — list on the left, detail on the right. Pass `list` and `detail` as separate props |
137| `CanvasLayout` | a one-page report, embed, or screen with no chrome at all |
138| `CenteredLayout` | login forms, onboarding screens, single-action surfaces |
139 
140Rules:
141- **Pick exactly one** shell per dashboard. Do not mix two shells in App.tsx.
142- 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?").
143- After swapping the shell, edit the nav entries inside that shell file to match the routes you wire up.
144- All shells share the same stone palette, typography, and spacing — do not introduce new colors or fonts when switching shells.
145- `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.
146 
147## Visual style rules
148 
149Design 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.
150 
151Concrete rules:
152- Use the stone color palette only — stone-tinted text, borders, and backgrounds. No blues, greens, purples, or rainbow palettes.
153- Prefer compact typography (extra-small or small text sizes) with a normal font weight. Avoid bold headlines except for the page title.
154- Keep borders minimal (single hairline in a light stone tone) and avoid heavy shadows or rounded "card" stacks. Flat surfaces only.
155- 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.
156- 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.
157- Empty states, loading states, and error states must follow the same stone palette and typography — no spinners with brand colours, no red error toasts.
158 
159When in doubt, look at the existing pages in the template and match their density, spacing, and tone before adding anything new.
160 
161## Build, run, and verify
162 
163The dashboard does **not** hot-reload on file changes. After editing
164source files under `/home/user/dashboard/src/...`, run `npm run build`
165from `/home/user/dashboard`. You do **not** need to restart the server
166for `src/` edits — the running Express server uses
167`express.static(dist/public)` and reads files from disk on every
168request, so the next iframe refresh picks up the freshly-built bundle
169automatically (Vite emits new hashed asset filenames; the new
170`index.html` points to them).
171 
172Default flow after a `src/` edit:
1731. Run `npm run build` from `/home/user/dashboard`.
1742. Tell the user the update is live and to refresh the App tab. Done.
175 
176**Do not stop, kill, or restart the dashboard server as part of the
177normal edit loop.** Doing so wastes time and creates a failure mode:
178the agent backgrounds `node server.js`, then polls the background task
179waiting for it to "complete" — but a healthy server is a long-lived
180process, so the poll loop never resolves and the chat appears frozen.
181 
182### When a restart IS warranted
183 
184Only restart the server when one of these is true:
185 
186- You edited `server.js` itself (new API route, new middleware, anything
187 that changes server behavior — Express won't pick those up without a
188 process restart).
189- The server isn't running on 3847. The platform's start flow is the
190 right tool for this — ask it to start; do not hand-roll a launch.
191- The user reports a visible problem with the dashboard (blank App tab,
192 stale UI even after refresh, an error they can see) and a restart is
193 a plausible fix. In that case: stop the old process using the
194 port-scoped cleanup pattern (never broad pkill inside the sandbox),
195 launch the new one, then verify health via the `/api/health` endpoint.
196 
197If you do launch `node server.js` yourself, fire-and-forget it
198(redirect output to a log file, return immediately) and verify via
199the `/api/health` endpoint. Never poll a background-task output waiting
200for the server process to exit — it won't.
201 
202### Verifying after work that warranted a restart
203 
204Hit the health endpoint:
205- ok=true with db=true: dashboard is live.
206- db=false: inform the user that agent DB credentials/config are missing and stop further DB-dependent work.
207 
208## Troubleshooting
209 
210If 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.
211 
2121. **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.
2132. **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.
2143. **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.
2154. **"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.
2165. **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.
2176. **Stale UI after a long session.** The build output diverged from source. Remove the `dist` folder inside the runnable project folder and rebuild.
2187. **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.
2198. **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.
220 
221If 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.
222 
223## Completion message
224 
225End with a direct status message that the dashboard is live in the App tab and ready
226for further edits.
227 
228## Iteration loop
229 
230For every follow-up tweak (this loop is the agent's job, not the user's):
2311. Edit relevant files at `/home/user/dashboard/...`. Symlinks persist edits to the workspace automatically; no sync step.
2322. Run `npm run build` from `/home/user/dashboard`. Do **not** restart the server — Express serves the new `dist/` automatically on the next request.
2333. Tell the user the update is done and ask them to refresh the App tab.
234 
235Restart 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).
236 

Discussion

Alternatives

Also in Reports & dashboards