hypercross 21fc1b29c1 feat(mesh): support uvBounds and planar back-face UVs
Add an optional uvBounds to extrudeShape/extrudeShapeParts so UVs can be derived from a framing rectangle instead of the shape's bounding box, letting a traced silhouette align with its full image. The bottom face now uses the same planar xy UVs as the top face (no horizontal mirror), so the texture is consistent across front, back, and walls.
2026-08-08 15:12:07 +08:00

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_KEY is optional. Search works without it. The mod page works without it too when you arrive from a search result: the frontend passes the item's file_url (already present in search results) to /items/:id via a fileUrl query param, so the proxy downloads the save directly instead of calling the Steam API. Without a key and without a fileUrl (e.g. a deep-linked mod page), /items/* returns 500.
  • Each request fetches fresh; there is no caching by design (client-only tool).
  • packages/extract has zero runtime dependencies and runs in browser or Node, ready for a future frontend.
S
Description
No description provided
Readme
2.5 MiB
Languages
TypeScript 98.8%
JavaScript 1%
HTML 0.2%