Pulse 3.8.0 is a Vite + React application for building and previewing animated map layers. It uses the ArcGIS Maps SDK for JavaScript and Calcite UI components to handle mapping, layer styling, and timeline-based animation controls.
Pulse 3 demo link -> https://seanst.one/demos/pulse3/
Pulse 2 link -> https://github.com/sean-stone/pulse Pulse 1 link -> https://github.com/EsriUK/pulse
- React 18 + TypeScript
- Vite 5
- ArcGIS JS API (
@arcgis/core,@arcgis/map-components) - Calcite Components (
@esri/calcite-components) - ESLint + Vitest
Install dependencies (runs a postinstall step that copies ArcGIS assets):
npm installThis app uses a PHP endpoint at agent/anim.php. For local dev, run both the Vite dev server and a PHP server.
- Start the PHP server (from the repo root so
/agent/anim.phpis available):
Windows PowerShell:
# From the repo root
$env:OPENAI_API_KEY="your_openai_key_here"
# Optional if you need CORS during dev:
$env:PULSE_ALLOWED_ORIGIN="http://localhost:5173"
npm run php:servemacOS/Linux (bash/zsh):
# From the repo root
export OPENAI_API_KEY="your_openai_key_here"
# Optional if you need CORS during dev:
export PULSE_ALLOWED_ORIGIN="http://localhost:5173"
npm run php:serveNotes:
- If
phpis not found, install PHP first (e.g. via your OS package manager). - Leave this terminal running; it hosts
http://localhost:8000.
If you see: 'php' is not recognized as an internal or external command, install PHP and add it to PATH.
Option A (recommended): Winget
winget search PHP.PHP
# Then install a specific version, for example:
winget install --id PHP.PHP.8.4 -eClose and reopen PowerShell, then verify:
php -vOption B: Chocolatey
choco install phpClose and reopen PowerShell, then verify:
php -v- Start Vite:
npm run dev- Ensure the Vite dev server can reach the PHP endpoint:
- Option A (recommended): add a Vite proxy for
/agenttohttp://localhost:8000. - Option B: serve the built app from the same origin as PHP (see production steps below).
- Option A (recommended): add a Vite proxy for
Start the dev server:
npm run devBuild for production:
npm run buildPreview the production build locally:
npm run previewIf you want the app and PHP endpoint on the same origin:
npm run buildThen serve the repo root with PHP so /dist and /agent share the same origin:
$env:OPENAI_API_KEY="your_openai_key_here"
php -S localhost:8000 -t .Open http://localhost:8000/dist/ in your browser.
Run linting and tests:
npm run lint
npm run testsrc/app code (controllers, components, styles, types)public/static assets served by Vitescripts/small build helpers (ArcGIS asset copy)dist/production build output
npm installrunsscripts/copy-arcgis-assets.cjsto copy ArcGIS assets intopublic/assets.- If map UI assets look missing, re-run
npm installornpm run postinstall.
Pulse includes an AI prompt flow that generates a ProjectSnapshot via agent/anim.php (OpenAI Responses API).
- Server-side only (PHP); no API keys in the browser.
- Model is configured in
agent/anim.phpor viaOPENAI_MODEL. - Response is strict JSON that matches the snapshot schema, with validation and guardrails.
Environment variables:
OPENAI_API_KEY(required)OPENAI_MODEL(optional, defaults togpt-4.1-mini)PULSE_ALLOWED_ORIGIN(optional, for CORS during dev)PULSE_SHARED_SECRET(optional, shared-secret header gate)
If ARCGIS_API_KEY is set, the server will try to convert prompts like:
“Drive from A to B …” into real driving routes using ArcGIS World Route.
How it works:
- Parse one or more
from A to Bpairs (max 5 per request). - Geocode A and B with ArcGIS World Geocoding.
- Request a route from ArcGIS World Route (true-shape output).
- Replace the polyline geometry with the routed path.
Notes:
- Routing only triggers if the prompt includes words like “drive/route/directions”.
- If routing fails, the server returns a 422 error (instead of silently falling back).
- Use one route per line for multi-route prompts.
Environment variable:
ARCGIS_API_KEY(required for routing)
Example routing prompt:
1. Drive from Paris to Frankfurt.
2. Drive from Frankfurt to Munich.
3. Drive Munich to Barcelona.
4. Drive Barcelona back to Paris.
This app uses two places for secrets:
- Server (.htaccess or hosting UI) for PHP:
# .htaccess (webspace)
SetEnv OPENAI_API_KEY "your_openai_key_here"
SetEnv OPENAI_MODEL "gpt-4.1-mini"
SetEnv PULSE_ALLOWED_ORIGIN "https://seanst.one"
SetEnv PULSE_SHARED_SECRET "your_long_random_string"
#SetEnv ARCGIS_API_KEY "your_arcgis_key_here"- Frontend build (.env) for the optional shared-secret header:
# .env (repo root, do NOT commit)
VITE_PULSE_SHARED_SECRET=your_long_random_string
Notes:
.envshould be git-ignored.- The shared secret is a lightweight gate. It helps block random POSTs, but it’s visible in built JS.
- Prompt max length: 4000 characters (server enforced)
- Animation duration capped at 60 seconds
- Routes per request capped at 5
- CORS can be locked to a single origin with
PULSE_ALLOWED_ORIGIN
- Phosphor Icons (https://phosphoricons.com), MIT License. This project uses
@phosphor-icons/web.
I am open to contributions. Please open a PR or issue and I will review.
We stayed in short loops:
- You described the UX issue or desired wording.
- I located the specific file(s) and made a narrow change.
- You verified the result and asked for refinements.
- I adjusted until it matched your intent.
-
Prompt: "When changing a style, can it say 'ok' instead of apply?"
- Change: Updated the label text in the style modal footer button.
- Human/Codex: You called out the wording; I found the button and swapped the label with minimal impact elsewhere.
-
Prompt: "When a user presses 'add animation' can it NOT toggle the animation menu closed? ... pressing it again should just highlight the animation settings please"
- Change: Prevented collapse on repeat press and added a brief highlight state to the settings panel.
- Human/Codex: You defined the UX; I wired the click behavior and added a subtle visual cue.
-
Prompt: "The default width of the timeline layer icons ... can you default it to maybe double the width?"
- Change: Updated the JS-controlled default width so it actually takes effect on load.
- Human/Codex: You pointed to the visual issue; I traced the runtime width source rather than only CSS.
This full repo I did not write a single line of code... it was done using codex and about 2-3 evenings. I think this would have taken months to do manually. Especially considering the amount of iterations and changes I made to the UI during the process. I thought I'd share the code still so you can see the output.