hypercross 835250abdd feat(mesh): render cards, tiles, and tokens with separated front/back/walls
Split extrudeShapeParts into front, back, and walls so each face can carry
its own material. Flip back textures left/right on the material so they are
not mirrored, and slice card faces from the deck sprite sheet via CardID.
Resolve deck config from the parent deck, which is authoritative over a
card's own CustomDeck. Add unit tests for card resolution, sprite UVs, and
the flip helper.
2026-08-08 17:41:59 +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
GET /trace?url=&mode=&format=&offset= Trace an image into a vector shape (BSON); offset insets/outsets in pixels

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%