Echo-Foxtrot-Works/topostack

0

stars

89

commits

TypeScript

primary language

Sep 15, 2026

updated

README

TopoStack

Turn a place you love into something you can make. TopoStack is a browser-based terrain studio for creating layered, laser-cut reliefs and flat topographic engravings from real elevation and map data.

Visit the website · Open the studio · Report a bug or share an idea

Illustration of stacked topographic terrain layers

What you can make

WorkflowControlsOutput
Layered reliefPhysical size, material thickness, vertical exaggeration, map details, and fabrication settingsMaster SVG, cut panels, matching engraving panels, and an assembly guide
Flat engravingPhysical size, contour density, index contours, linework, map details, and borderOne SVG at physical size, containing engraving paths only

Both workflows support rectangular and circular crops; roads, trails, transportation labels, water outlines and fill patterns; state/province boundaries; latitude/longitude grids; elevation labels; a compass; and a scale bar. Add custom coordinate-based markers, trails, and boundaries to make a map your own.

Layered projects also support modeled lake depth, alignment guides, and material reuse. Sheet count is calculated from terrain relief, map scale, vertical exaggeration, and material thickness. Preview a project on the map, as 2D cut layers, as an engraving, or as a stacked/exploded 3D model, depending on the output type.

Get started

  1. Open the studio and explore the bundled Crater Lake preview.
  2. Choose a place, frame the map area, and select Layered or Flat output.
  3. Set the physical dimensions and details, then Generate terrain and inspect the result.
  4. Open Export to download the complete project, individual artwork, or project settings.

The initial preview uses a bundled snapshot of real terrain and map data. Generate fresh terrain before fabrication export. SVGs use physical millimeter coordinates; layered artwork separates red cuts, blue scores, and black engravings. Exports include project metadata and source attribution. Review the artwork and machine settings in your laser software before making a piece.

Project settings are saved in your browser's IndexedDB. Export a project-settings JSON backup to keep a copy or move to another device; import it using the studio's import control. Restored or imported projects need fresh terrain generation before fabrication export. Settings backups are available even when fabrication export is blocked.

The homepage lives at /, the editor at /studio, and the former /about URL redirects to /. TopoStack also supports the Atomm export lifecycle and Open in Studio integration.

Built in the open, with AI

TopoStack is a solo developer's spare-time project under Echo Foxtrot Works, unashamedly built with help from AI. That collaboration helps turn ideas into working software and make the most of the time available.

Explore the code, ask questions, suggest improvements, or contribute through GitHub. For bugs, include the output type, selected location, reproduction steps, and browser details; a project-settings JSON file can help reproduce geometry problems. For code changes, explain the behavior you changed and how you verified it. Pull requests normally target dev; releases are promoted to main.

Donations help support development and are always optional. Every export is available without donating.

Local development

Install

Use the Node version in .nvmrc, currently 22.22.2, to match CI. The supported runtime ranges are declared in package.json.

git clone https://github.com/Echo-Foxtrot-Works/topostack.git
cd topostack
nvm install
nvm use
npm ci

If you use another Node version manager, select the version from .nvmrc before installing dependencies. Run the following commands from the repository root.

Frontend and bundled preview

npm run dev:web

Open the URL printed by Vite, normally localhost:5273. This is enough to work on the homepage, studio UI, and bundled preview without Cloudflare credentials. Live place search and generation require a reachable map API. If terrain loading fails, the studio can display synthetic fallback terrain, but fabrication export remains blocked.

Frontend and local map API

npm run dev

This starts the API, waits for /health, then starts Vite. The default ports are 8787 for the API and 5273 for the frontend. The launcher tries the next available ports when a default is busy and connects the frontend to the chosen API port.

The local Worker simulates terrain-cache storage but reads the provisioned vector and lake archives from the remote development R2 bucket. Full data access therefore requires Wrangler authentication and access to those Cloudflare resources. For your own deployment, follow the map API setup guide and data setup below.

Place search additionally needs a Geoapify key:

cp workers/map-api/.dev.vars.example workers/map-api/.dev.vars

Set GEOCODER_API_KEY in that local file. The geocoder key is not required to fetch elevation for known coordinates. .dev.vars is ignored by Git.

Configuration

VariablePurpose
TOPOSTACK_WEB_PORTFrontend port; defaults to 5273
VITE_MAP_API_PORTLocal API port; defaults to 8787
VITE_MAP_API_URLExplicit API origin; overrides the local API URL. Use a reachable deployment that permits your frontend origin.
VITE_DONATION_URLOptional donation destination; defaults to the TopoStack PayPal page
GEOCODER_API_KEYWorker-only Geoapify credential; keep it in .dev.vars locally or a deployment secret

Pass port overrides in the shell:

VITE_MAP_API_PORT=8799 TOPOSTACK_WEB_PORT=5299 npm run dev

Explicitly selected busy ports cause an error. The individual dev:web and dev:api commands honor their port variables but do not search for a free port. Set frontend API/donation overrides in apps/generator/.env or the shell; VITE_ values are included in the browser build and must not contain secrets.

Repository layout

PathResponsibility
apps/generatorSvelte 5/SvelteKit homepage and studio, previews, browser storage, downloads, and Atomm integration
packages/corePortable TypeScript geometry engine, fabrication planning, and SVG generation
workers/map-apiCloudflare Worker for terrain, map archives, geocoding, caching, and readiness checks
e2eDeterministic Playwright tests for navigation, previews, generation, and exports
e2e-liveBrowser canary that generates and exports against a deployed API
scriptsDevelopment launcher, data provisioning, build budgets, and release verification
atommPlatform listing and cover artwork
docsArchitecture, fabrication details, and operational runbooks

Terrain geometry is calculated in the browser, with expensive work delegated to a Web Worker. The API streams and caches source data. MapLibre supplies the interactive reference map, and Three.js renders the 3D preview. See architecture for the geometry pipeline and coordinate conventions.

Validation

npm run lint
npm run typecheck
npm test
npm run build
npm run budget:web

npm run build builds all workspaces, including a dry run of the Worker deployment; it does not publish the app. The generated frontend is in apps/generator/dist. npm run budget:web checks that built output against separate homepage/editor JavaScript budgets, startup costs, total assets, and HTML limits.

Install browsers before running the end-to-end suite:

npx playwright install chromium firefox webkit
npm run test:e2e

The browser suite builds its own deterministic test version and covers Chromium, Firefox, and WebKit. npm run test:coverage runs the unit/component/Worker suites with the thresholds used in CI. Run the live browser canary against a deployed environment with:

PUBLIC_APP_URL=https://dev-topostack.echofoxtrot.works npm run test:e2e:live

If local lint reports files under .wrangler/tmp, exclude those generated files with npx eslint . --max-warnings=0 --ignore-pattern '**/.wrangler/**'. CI uses a clean checkout.

Deployment and releases

GitHub Actions validates pull requests targeting dev or main. Successful pushes to those branches, or manual runs on them, deploy the matching environment after quality, build, and browser checks pass.

BranchGitHub environmentWorkerWebsite
devdevelopmenttopostack-devDevelopment
mainproductiontopostackProduction

Each Worker serves the static frontend and /v1/* API, with /health for liveness and /ready for data/geocoder readiness. Configure these secrets separately in both GitHub environments:

  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN
  • GEOCODER_API_KEY

Restrict development deployments to dev and production to main. Configure any production reviewer gate to work with your maintainer team. Pull-request validation does not reference deployment environments; only the deployment job receives their secrets. See the Worker guide for resource configuration and token permissions.

The hourly production monitor checks the frontend, API, and data-serving paths. The daily browser monitor generates real terrain and inspects a downloaded fabrication package. Use the release acceptance and rollback runbook for platform, physical fabrication, and recovery checks.

Atomm

To preview the local studio in Atomm, use the running frontend URL as its local parameter. With the default port:

https://www.atomm.com/creativetools/community/generator/topographic-map-generator?local=http://localhost:5273/studio

Build a release against the deployed production API:

VITE_MAP_API_URL=https://topostack.echofoxtrot.works npm run release:atomm

This produces apps/generator/topostack-atomm.zip, its .zip.sha256 checksum, and topostack-atomm.release.json with the source revision, API origin, dataset/archive identities, and dirty-tree flag. Packaging requires a real HTTPS API origin and rejects local, placeholder, and *.workers.dev URLs. Use npm run package:atomm with the same API variable for the ZIP and validation without the checksum/receipt step.

After a successful production deployment and smoke test, CI uploads the ZIP, checksum, receipt, cover image, and listing as a topostack-atomm-<commit> artifact retained for 30 days. Publish from a clean commit and keep the release evidence with the data-provisioning receipts.

Data sources and provisioning

  • Elevation: Mapzen Terrarium tiles, cached in R2 with imagery-source attribution.
  • Roads, trails, water, and boundaries: the pinned Protomaps/OpenStreetMap PMTiles archive at osm/current.pmtiles.
  • Lake depth: HydroLAKES and GLOBathy data at lakes/current.pmtiles; lake basins are modeled, with per-lake depth overrides in the studio.
  • Place search: Geoapify, proxied through the Worker.
  • Reference map: OpenFreeMap; reference-map imagery is not included in fabrication exports.

Archive provisioning is separate from application deployment. Use provision-vector-data.mjs for the map archive, and build-lake-data.mjs plus provision-lake-data.mjs for lake data. The lake build requires Tippecanoe; archive verification requires the PMTiles CLI. The provisioning scripts require an archive path, --provision, Cloudflare credentials, and a pinned SHA-256 digest. A development-only --skip-digest-check option supports establishing a new archive pin.

Provisioning writes development by default. --prod writes both development and production, requires a pinned digest, and overwrites the configured archive keys. Retain the original archives and emitted provisioning receipts for rollback. The Worker guide documents the map archive and resource setup.

/ready requires both archives and the geocoder configuration. Fabrication export requires current real terrain and any requested map/lake data; missing or truncated requested data blocks export. The bundled preview and synthetic fallback are for previewing and development.

Terrain and map data are decorative source material, not survey, navigation, or engineering data. Exported projects include attribution; see data and fabrication notes and the maintained source-credit list.

Further reading

License status

A project LICENSE file has not been added to this repository yet. Dependency and map-data licenses are documented separately in their packages and the source credits.

Contributors

loidolt

80 commits

Echo-Foxtrot-Works/topostack

0

stars

89

commits

TypeScript

primary language

Sep 15, 2026

updated

README

TopoStack

Turn a place you love into something you can make. TopoStack is a browser-based terrain studio for creating layered, laser-cut reliefs and flat topographic engravings from real elevation and map data.

Visit the website · Open the studio · Report a bug or share an idea

Illustration of stacked topographic terrain layers

What you can make

WorkflowControlsOutput
Layered reliefPhysical size, material thickness, vertical exaggeration, map details, and fabrication settingsMaster SVG, cut panels, matching engraving panels, and an assembly guide
Flat engravingPhysical size, contour density, index contours, linework, map details, and borderOne SVG at physical size, containing engraving paths only

Both workflows support rectangular and circular crops; roads, trails, transportation labels, water outlines and fill patterns; state/province boundaries; latitude/longitude grids; elevation labels; a compass; and a scale bar. Add custom coordinate-based markers, trails, and boundaries to make a map your own.

Layered projects also support modeled lake depth, alignment guides, and material reuse. Sheet count is calculated from terrain relief, map scale, vertical exaggeration, and material thickness. Preview a project on the map, as 2D cut layers, as an engraving, or as a stacked/exploded 3D model, depending on the output type.

Get started

  1. Open the studio and explore the bundled Crater Lake preview.
  2. Choose a place, frame the map area, and select Layered or Flat output.
  3. Set the physical dimensions and details, then Generate terrain and inspect the result.
  4. Open Export to download the complete project, individual artwork, or project settings.

The initial preview uses a bundled snapshot of real terrain and map data. Generate fresh terrain before fabrication export. SVGs use physical millimeter coordinates; layered artwork separates red cuts, blue scores, and black engravings. Exports include project metadata and source attribution. Review the artwork and machine settings in your laser software before making a piece.

Project settings are saved in your browser's IndexedDB. Export a project-settings JSON backup to keep a copy or move to another device; import it using the studio's import control. Restored or imported projects need fresh terrain generation before fabrication export. Settings backups are available even when fabrication export is blocked.

The homepage lives at /, the editor at /studio, and the former /about URL redirects to /. TopoStack also supports the Atomm export lifecycle and Open in Studio integration.

Built in the open, with AI

TopoStack is a solo developer's spare-time project under Echo Foxtrot Works, unashamedly built with help from AI. That collaboration helps turn ideas into working software and make the most of the time available.

Explore the code, ask questions, suggest improvements, or contribute through GitHub. For bugs, include the output type, selected location, reproduction steps, and browser details; a project-settings JSON file can help reproduce geometry problems. For code changes, explain the behavior you changed and how you verified it. Pull requests normally target dev; releases are promoted to main.

Donations help support development and are always optional. Every export is available without donating.

Local development

Install

Use the Node version in .nvmrc, currently 22.22.2, to match CI. The supported runtime ranges are declared in package.json.

git clone https://github.com/Echo-Foxtrot-Works/topostack.git
cd topostack
nvm install
nvm use
npm ci

If you use another Node version manager, select the version from .nvmrc before installing dependencies. Run the following commands from the repository root.

Frontend and bundled preview

npm run dev:web

Open the URL printed by Vite, normally localhost:5273. This is enough to work on the homepage, studio UI, and bundled preview without Cloudflare credentials. Live place search and generation require a reachable map API. If terrain loading fails, the studio can display synthetic fallback terrain, but fabrication export remains blocked.

Frontend and local map API

npm run dev

This starts the API, waits for /health, then starts Vite. The default ports are 8787 for the API and 5273 for the frontend. The launcher tries the next available ports when a default is busy and connects the frontend to the chosen API port.

The local Worker simulates terrain-cache storage but reads the provisioned vector and lake archives from the remote development R2 bucket. Full data access therefore requires Wrangler authentication and access to those Cloudflare resources. For your own deployment, follow the map API setup guide and data setup below.

Place search additionally needs a Geoapify key:

cp workers/map-api/.dev.vars.example workers/map-api/.dev.vars

Set GEOCODER_API_KEY in that local file. The geocoder key is not required to fetch elevation for known coordinates. .dev.vars is ignored by Git.

Configuration

VariablePurpose
TOPOSTACK_WEB_PORTFrontend port; defaults to 5273
VITE_MAP_API_PORTLocal API port; defaults to 8787
VITE_MAP_API_URLExplicit API origin; overrides the local API URL. Use a reachable deployment that permits your frontend origin.
VITE_DONATION_URLOptional donation destination; defaults to the TopoStack PayPal page
GEOCODER_API_KEYWorker-only Geoapify credential; keep it in .dev.vars locally or a deployment secret

Pass port overrides in the shell:

VITE_MAP_API_PORT=8799 TOPOSTACK_WEB_PORT=5299 npm run dev

Explicitly selected busy ports cause an error. The individual dev:web and dev:api commands honor their port variables but do not search for a free port. Set frontend API/donation overrides in apps/generator/.env or the shell; VITE_ values are included in the browser build and must not contain secrets.

Repository layout

PathResponsibility
apps/generatorSvelte 5/SvelteKit homepage and studio, previews, browser storage, downloads, and Atomm integration
packages/corePortable TypeScript geometry engine, fabrication planning, and SVG generation
workers/map-apiCloudflare Worker for terrain, map archives, geocoding, caching, and readiness checks
e2eDeterministic Playwright tests for navigation, previews, generation, and exports
e2e-liveBrowser canary that generates and exports against a deployed API
scriptsDevelopment launcher, data provisioning, build budgets, and release verification
atommPlatform listing and cover artwork
docsArchitecture, fabrication details, and operational runbooks

Terrain geometry is calculated in the browser, with expensive work delegated to a Web Worker. The API streams and caches source data. MapLibre supplies the interactive reference map, and Three.js renders the 3D preview. See architecture for the geometry pipeline and coordinate conventions.

Validation

npm run lint
npm run typecheck
npm test
npm run build
npm run budget:web

npm run build builds all workspaces, including a dry run of the Worker deployment; it does not publish the app. The generated frontend is in apps/generator/dist. npm run budget:web checks that built output against separate homepage/editor JavaScript budgets, startup costs, total assets, and HTML limits.

Install browsers before running the end-to-end suite:

npx playwright install chromium firefox webkit
npm run test:e2e

The browser suite builds its own deterministic test version and covers Chromium, Firefox, and WebKit. npm run test:coverage runs the unit/component/Worker suites with the thresholds used in CI. Run the live browser canary against a deployed environment with:

PUBLIC_APP_URL=https://dev-topostack.echofoxtrot.works npm run test:e2e:live

If local lint reports files under .wrangler/tmp, exclude those generated files with npx eslint . --max-warnings=0 --ignore-pattern '**/.wrangler/**'. CI uses a clean checkout.

Deployment and releases

GitHub Actions validates pull requests targeting dev or main. Successful pushes to those branches, or manual runs on them, deploy the matching environment after quality, build, and browser checks pass.

BranchGitHub environmentWorkerWebsite
devdevelopmenttopostack-devDevelopment
mainproductiontopostackProduction

Each Worker serves the static frontend and /v1/* API, with /health for liveness and /ready for data/geocoder readiness. Configure these secrets separately in both GitHub environments:

  • CLOUDFLARE_ACCOUNT_ID
  • CLOUDFLARE_API_TOKEN
  • GEOCODER_API_KEY

Restrict development deployments to dev and production to main. Configure any production reviewer gate to work with your maintainer team. Pull-request validation does not reference deployment environments; only the deployment job receives their secrets. See the Worker guide for resource configuration and token permissions.

The hourly production monitor checks the frontend, API, and data-serving paths. The daily browser monitor generates real terrain and inspects a downloaded fabrication package. Use the release acceptance and rollback runbook for platform, physical fabrication, and recovery checks.

Atomm

To preview the local studio in Atomm, use the running frontend URL as its local parameter. With the default port:

https://www.atomm.com/creativetools/community/generator/topographic-map-generator?local=http://localhost:5273/studio

Build a release against the deployed production API:

VITE_MAP_API_URL=https://topostack.echofoxtrot.works npm run release:atomm

This produces apps/generator/topostack-atomm.zip, its .zip.sha256 checksum, and topostack-atomm.release.json with the source revision, API origin, dataset/archive identities, and dirty-tree flag. Packaging requires a real HTTPS API origin and rejects local, placeholder, and *.workers.dev URLs. Use npm run package:atomm with the same API variable for the ZIP and validation without the checksum/receipt step.

After a successful production deployment and smoke test, CI uploads the ZIP, checksum, receipt, cover image, and listing as a topostack-atomm-<commit> artifact retained for 30 days. Publish from a clean commit and keep the release evidence with the data-provisioning receipts.

Data sources and provisioning

  • Elevation: Mapzen Terrarium tiles, cached in R2 with imagery-source attribution.
  • Roads, trails, water, and boundaries: the pinned Protomaps/OpenStreetMap PMTiles archive at osm/current.pmtiles.
  • Lake depth: HydroLAKES and GLOBathy data at lakes/current.pmtiles; lake basins are modeled, with per-lake depth overrides in the studio.
  • Place search: Geoapify, proxied through the Worker.
  • Reference map: OpenFreeMap; reference-map imagery is not included in fabrication exports.

Archive provisioning is separate from application deployment. Use provision-vector-data.mjs for the map archive, and build-lake-data.mjs plus provision-lake-data.mjs for lake data. The lake build requires Tippecanoe; archive verification requires the PMTiles CLI. The provisioning scripts require an archive path, --provision, Cloudflare credentials, and a pinned SHA-256 digest. A development-only --skip-digest-check option supports establishing a new archive pin.

Provisioning writes development by default. --prod writes both development and production, requires a pinned digest, and overwrites the configured archive keys. Retain the original archives and emitted provisioning receipts for rollback. The Worker guide documents the map archive and resource setup.

/ready requires both archives and the geocoder configuration. Fabrication export requires current real terrain and any requested map/lake data; missing or truncated requested data blocks export. The bundled preview and synthetic fallback are for previewing and development.

Terrain and map data are decorative source material, not survey, navigation, or engineering data. Exported projects include attribution; see data and fabrication notes and the maintained source-credit list.

Further reading

License status

A project LICENSE file has not been added to this repository yet. Dependency and map-data licenses are documented separately in their packages and the source credits.

Contributors

loidolt

80 commits

Languages

TypeScript

63.4%

Svelte

22.9%

JavaScript

8.1%

CSS

5.6%