This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Spotmap is a WordPress plugin that displays GPS tracking data from SPOT devices on interactive Leaflet maps. It provides a Gutenberg block and shortcodes for embedding maps in posts/pages.
Never run npm run env:start or assume the containers are stopped. The wp-env Docker containers are kept running during development β if a wp-env run cli command fails, diagnose the command, not the container state. The DB is MariaDB; use docker exec to run queries directly:
docker exec <hash>-mysql-1 mariadb -u root -ppassword wordpress -e "SELECT ..."Find the container name with docker ps.
- This project uses 4 spaces for indentation throughout β never tabs.
- When using the Edit tool, use 4-space indentation in
old_string/new_string. - Run
npm run format(JS/TS/CSS) andnpm run format:php(PHP) to auto-format code.
# Development
npm run start
# Production build
npm run build
# WordPress environment (Docker)
npm run env:start
npm run env:stop
# After env:destroy + env:start, create the test DB once:
npm run wp-env -- run cli bash -- -c "mysql -h mysql -u root -ppassword -e 'CREATE DATABASE IF NOT EXISTS wordpress_test'"
# Tests (run inside the wp-env Docker container)
npm run test:js # Jest unit tests (TypeScript/JS)
npm run test:php # PHP unit tests (~31 s)
npm run composer -- run test:coverage # PHP coverage report
# Lint
npm run lint:js
npm run lint:css
npm run lint:php
# Format
npm run format # JS/TS/CSS (Prettier via wp-scripts)
npm run format:php # PHP (php-cs-fixer via wp-env; requires env:start + composer install)
# Package plugin zip
npm run plugin-zipnpm run build runs copy-deps:prod (strips source maps) then wp-scripts build.
npm run start runs copy-deps (preserves source maps) then wp-scripts start.
PHP tests live in tests/ and run against a real WordPress environment (requires npm run env:start). JS tests in src/**/__tests__/ run standalone via Jest.
| File | Role |
|---|---|
spotmap.php |
Entry point β instantiates Spotmap class |
includes/class-spotmap.php |
Orchestrator β loads dependencies, registers hooks via Spotmap_Loader |
includes/class-spotmap-options.php |
Admin options and marker defaults |
includes/class-spotmap-database.php |
DB layer β table wp_spotmap_points |
includes/class-spotmap-api-crawler.php |
Fetches data from SPOT API |
admin/class-spotmap-admin.php |
Admin settings page |
public/class-spotmap-public.php |
Enqueues scripts, registers shortcodes |
public/render-block.php |
Server-side renderer for the Gutenberg dynamic block |
Composer dependency symfony/yaml is vendor-prefixed under the Spotmap\ namespace via Strauss.
Two webpack entry points (configured in webpack.config.js):
-
src/spotmap/β Gutenberg block (React/JSX)block.jsonβ block definition, 17 attributes, API v3, dynamic block (save returns null)edit.jsxβ ~33KB React editor component with live preview- Block is registered via
build/spotmap/byregister_block_type()
-
src/map-engine/β TypeScript map engine compiled tobuild/spotmap-map/index.jsindex.tsβ exposeswindow.SpotmapSpotmap.tsβ main class,initMap()entry pointDataFetcher.ts,LayerManager.ts,MarkerManager.ts,LineManager.ts,BoundsManager.ts,ButtonManager.ts,TableRenderer.tstypes.tsβ all TypeScript interfaces (SpotmapOptions,SpotPoint,FeedStyle,GpxTrackConfig, etc.)
render-block.php outputs a <div> with an inline <script> that calls new Spotmap(options).initMap(). Options are JSON-encoded block attributes. The map engine fetches GPS points via AJAX (wp_ajax_spotmap).
The editor (edit.jsx) calls window.Spotmap directly for the live preview, using spotmapjsobj (localized via wp_localize_script) which contains ajaxUrl, maps, overlays, feeds, defaultValues, and marker config.
scripts/copy-deps.js copies npm packages (Leaflet, Leaflet plugins, Font Awesome) from node_modules/ into public/. The public/ directory is generated β do not edit files there directly.
Key deps: leaflet, leaflet-fullscreen, leaflet-gpx, leaflet-easybutton, leaflet-textpath, leaflet.tilelayer.swiss, beautifymarker, @fortawesome/fontawesome-free.
Table wp_spotmap_points:
id, type, time, latitude, longitude, altitude, battery_status, message, custom_message, feed_name, feed_id, model, device_name, local_timezone
config/maps.yaml defines 25+ tile layer providers. Loaded server-side, passed to the block editor and frontend via spotmapjsobj.
-
latestUnixtimeByFeedredundant state (Spotmap.ts): TheMap<string, number>tracking the latest unixtime per feed duplicatesfeed.points.at(-1)?.unixtime, sinceMarkerManager.addPoint()already pushes tofeed.points. Refactor the auto-reload polling loop to readfeed.pointsdirectly and remove the Map. -
Duplicated polling pattern:
Spotmap.tsandTableRenderer.tsshare ~70 lines of identical timeout/visibility-change polling logic. Consider extracting aVisibilityAwarePollerutility class intoutils.ts. -
Feed style defaults should move to the map engine:
render-block.phpand the shortcode both pre-populate per-feedstyles(color, splitLines) from WP admin defaults in PHP. IdeallyLayerManager.getFeedColor()andgetFeedSplitLines()would fall back tospotmapjsobj.defaultValues(already available at runtime) and cycle colors by feed index fromoptions.feeds, allowing both renderers to pass a sparse/emptystyles. The PHP pre-population inrender-block.phpwas added to match shortcode behaviour for now. -
move maps.yaml into wp_options table? and potentially make it so that the user can modify/add via GUI?
-
the ajax call to retrieve points should be prefixed with spotmap_
-
[perf T2] Admin: DB pruning action: Add an admin button that previews how many points would be removed per feed by a Douglas-Peucker simplification pass (Ξ΅ configurable), then executes the prune on confirmation. Run against points older than N days to preserve recent full-resolution data.
-
add phpunit test with sample data to rename a feed - what happens if the feedname already exists? (this might wanted but on the UI we should get a warning that must be accepted)
- Dynamic block:
save()returnsnull; all rendering is inrender-block.php. - Marker shape/icon config belongs in WP admin (Options), not the block editor UI.
public/is generated β edit source files insrc/andnode_modules/and rebuild.- No
"type": "commonjs"inpackage.jsonβ this is intentional. - Vendor prefix: Composer packages live under
Spotmap\namespace via Strauss; regenerate withcomposer installthencomposer exec strauss.