Add per-class 3D viewers for tiles, tokens, cards, and custom models using React Three Fiber, drei, and postprocessing. Viewers are lazy-loaded and registered through the existing viewer registry, with a shared scene wrapper for lighting, orbit controls, and subtle effects. Add a CORS-safe /asset proxy route so three.js loaders can fetch Workshop-hosted textures and models, and extend TTSObject with the CustomMesh and CustomTile/CustomToken fields the viewers read.
5.6 KiB
Architecture & Dependencies
Scope: The system's architecture and dependency graph. For implementation details (files, endpoints, build order), see
implementation-plan.md. For the rationale behind key decisions, seedecisions.md.
Overview
A lightweight, client-only pnpm monorepo that lets a user search the Tabletop Simulator Steam Workshop, fetch full TTS save files, and analyze their contents. It is split into four packages with a strict layering: a thin HTTP proxy on top, a low-level fetcher, an isomorphic analysis layer, and a shared types/validation package.
Design principles
- Client-only & lightweight — no caching layer, no shared server state.
- Fetch vs analyze separation —
packages/ttsonly fetches and parses;packages/extractonly analyzes. Neither depends on the other's concerns. - Isomorphic analysis —
packages/extractruns in browser and Node, using onlyfetch,Blob, and typed arrays (noBuffer, no Node-only packages). - Thin proxy — the HTTP layer exposes search and fetch only; traversal is intentionally not exposed as endpoints.
Package responsibilities
| Package | Role | Runtime |
|---|---|---|
apps/web |
React frontend: search + mod pages | Browser |
apps/proxy |
Hono HTTP server: search + fetch endpoints | Node |
packages/tts |
Fetch save from Steam, BSON-parse to TTSMod |
Node |
packages/extract |
Analyze a TTSMod: objects, refs, assets |
Isomorphic |
packages/shared |
Shared types + zod schemas | Isomorphic |
Dependency graph
apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
│ │ │
│ │ └──► (fetchMod → TTSMod)
│ ▼
└──► packages/extract ──► packages/shared (types)
Edges
apps/web→apps/proxy— calls/search,/items/:id,/items/:id/file, and/asset(CORS-safe asset proxy) over HTTP.apps/web→packages/extract— usesbuildTree/collectRefsto analyze a loadedTTSModin the browser (tree sidebar + asset refs).apps/proxy→packages/tts— callsfetchMod/getFileNameto serve item requests.apps/proxy→packages/shared— uses shared types and zod schemas for request/response validation.packages/tts→packages/shared— consumesTTSMod/TTSObjecttypes.packages/extract→packages/tts— reusestraverseMod(traversal logic lives inextract; see note below).packages/extract→packages/shared— consumes shared types.
Note on
traverseMod: traversal is analysis, so it lives inpackages/extract.packages/ttsis fetch-only. The graph edgeextract → ttsreflects thatextractimports the traversal helper that was originally authored alongside the fetcher;ttsdoes not depend onextract.
Layering rules
- No upward dependencies —
packages/*never importapps/*. - No sibling coupling beyond the graph above —
extractandttsdo not depend on each other's analysis/fetch concerns. packages/sharedis the leaf — everything depends on it; it depends on nothing internal.
External dependencies
| Package | Purpose | Used by |
|---|---|---|
hono |
HTTP framework | apps/proxy |
@hono/node-server |
Node adapter for Hono | apps/proxy |
@hono/cors |
CORS middleware | apps/proxy |
bson |
BSON deserialization of TTS save files | packages/tts |
cheerio |
Workshop browse page scraping | apps/proxy |
zod |
Runtime validation | apps/proxy, packages/shared |
three |
3D rendering | apps/web |
@react-three/fiber |
React renderer for three.js | apps/web |
@react-three/drei |
three.js helpers (controls, textures) | apps/web |
@react-three/postprocessing |
Post-processing effects | apps/web |
Runtime constraints
cheeriois backend-only — it never appears inpackages/extract, which must stay isomorphic.bsonis Node-only — used by the fetcher, not the analysis layer.packages/extracthas zero external runtime deps — it relies only on platformfetch/Blob, keeping it portable to a future frontend.
Tooling dependencies
typescript(strict),tsx(dev runner),eslint,prettier.vitest(unit tests), colocated as*.test.tsnext to sources.vite,@vitejs/plugin-react,tailwindcss,@tailwindcss/vite— frontend build/dev tooling.pnpmworkspaces for package management.
Deployment / runtime shape
- The proxy runs as a single Node process via
@hono/node-server. apps/webis a static Vite build served separately; during development it proxies/search,/items,/health, and/assetto the proxy.packages/extractis published/consumed as a plain ESM module usable from a browser bundle or Node.- No shared state between requests; each request fetches fresh.