Replace the per-shape tile geometries with @tts/mesh extrusion, honoring the tile type and stretch aspect ratio. Add extrudeShapeParts so the caps carry the texture while the walls are solid white, matching TTS tinting. Fix bottom-face UVs to mirror horizontally and make wall UVs inherit the front mapping independent of z.
TTS Workshop
Search the Tabletop Simulator Steam Workshop, fetch full TTS save files, and analyze their contents. A lightweight, client-only pnpm monorepo.
Packages
| Package | Role | Runtime |
|---|---|---|
apps/web |
React frontend: search + mod pages | Browser |
apps/proxy |
Hono HTTP server: Workshop search + save fetch | Node |
packages/tts |
Fetch save from Steam, BSON-parse to TTSMod |
Node |
packages/extract |
Analyze a TTSMod: objects, asset refs, downloads |
Isomorphic |
packages/shared |
Shared types + zod schemas | Isomorphic |
See docs/architecture.md for the architecture and
docs/implementation-plan.md for the plan.
Setup
pnpm install
cp .env.example .env # then set STEAM_API_KEY
pnpm dev # runs the proxy at http://localhost:3000
Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free).
To run the frontend alongside the proxy, open a second terminal and run
pnpm --filter @tts/web dev (serves at http://localhost:5173 and proxies
/search, /items, and /health to the backend).
API
| Method | Path | Description |
|---|---|---|
| GET | /health |
Liveness |
| GET | /search?q=&page= |
Search the Workshop (scrapes browse page) |
| GET | /items/:id |
Full parsed TTSMod (BSON save) |
| GET | /items/:id/file |
Raw save bytes, filename from header |
Commands
pnpm dev # run the proxy (tsx watch)
pnpm dev:web # run the frontend (vite)
pnpm build # compile all packages
pnpm typecheck # typecheck all packages
pnpm test # run the unit tests (vitest)
pnpm lint # lint all packages
How search works
Steam has no official search API. The proxy fetches the Workshop browse page
(steamcommunity.com/workshop/browse/?appid=286160) and parses the embedded
window.SSR.renderContext JSON (a React Query cache containing a
workshop_browse entry with the results). This is more robust than scraping
the DOM, but Steam can still change the page structure — if search breaks, that
parser is the first place to look.
Notes
STEAM_API_KEYis optional. Search works without it. The mod page works without it too when you arrive from a search result: the frontend passes the item'sfile_url(already present in search results) to/items/:idvia afileUrlquery param, so the proxy downloads the save directly instead of calling the Steam API. Without a key and without afileUrl(e.g. a deep-linked mod page),/items/*returns 500.- Each request fetches fresh; there is no caching by design (client-only tool).
packages/extracthas zero runtime dependencies and runs in browser or Node, ready for a future frontend.