# Decisions > **Scope:** The rationale behind key design decisions. For the system's > architecture, see [`architecture.md`](./architecture.md). For the concrete > build plan, see [`implementation-plan.md`](./implementation-plan.md). > > Each entry records the decision, the context, and the alternatives considered. > New entries are appended; existing entries are updated only to correct facts, > not to rewrite history. ## D1 — Client-only, no caching **Decision:** The system runs on the client only and has no caching layer. **Context:** The tool is a personal, single-user utility. There is no shared state to protect and no multi-instance deployment. **Alternatives considered:** In-memory TTL caching; Redis-backed caching. Both rejected as unnecessary complexity for a single client. ## D2 — Separate `packages/extract` from `packages/tts` **Decision:** Analysis lives in `packages/extract`; fetching lives in `packages/tts`. They are separate packages with a clean fetch-vs-analyze boundary. **Context:** The user wants to analyze saves and extract objects for a future frontend. Keeping analysis separate lets it be reused independently of the fetcher. **Alternatives considered:** Folding analysis into `packages/tts`. Rejected — couples fetch and analysis and would force the frontend to depend on the fetcher. ## D3 — Traversal lives in `packages/extract` **Decision:** `traverseMod` / `markParent` move from the fetcher into `packages/extract`. **Context:** Traversal is analysis, not fetching. `packages/tts` should be fetch-only. **Alternatives considered:** Keeping traversal in `packages/tts`. Rejected — blurs the fetch/analyze boundary. ## D4 — `packages/extract` is isomorphic with zero runtime deps **Decision:** `packages/extract` runs in browser and Node, using only `fetch`, `Blob`, and typed arrays. It has no external runtime dependencies. **Context:** The frontend (deferred) will consume it directly, so it must be portable. Node 18+ provides global `fetch` and `Blob`. **Alternatives considered:** Using `Buffer` and Node-only packages. Rejected — breaks browser use. ## D5 — No backend traversal endpoints **Decision:** The proxy exposes search and fetch only. Traversal is not exposed as HTTP endpoints. **Context:** The frontend will use `packages/extract` directly. Exposing traversal on the backend would duplicate that logic and add endpoints with no current consumer. **Alternatives considered:** Adding `/items/:id/objects` and similar endpoints. Deferred until a consumer exists. ## D6 — Search scrapes the Workshop browse page **Decision:** Search is implemented by scraping `steamcommunity.com/workshop/browse/?appid=286160` with `cheerio`. **Context:** Steam has no official Web API for searching the Workshop; the API only fetches details for known IDs. **Alternatives considered:** None viable — there is no official search API. Accepted risk: scraping is fragile and may break if Steam changes its HTML. ## D7 — `bson` npm package replaces the browser `BSON` global **Decision:** The existing scraper's `BSON.deserialize` global is replaced with the `bson` npm package. **Context:** The scraper was written for the browser; the proxy runs on Node. **Alternatives considered:** Keeping a browser-only global. Rejected — not available in Node. ## D8 — Download output type is `Blob` **Decision:** `packages/extract`'s `downloadAsset` / `downloadAll` return `Blob`. **Context:** The primary consumer is a frontend that renders assets in `` / `` elements. **Alternatives considered:** Raw `ArrayBuffer`. Rejected — less convenient for frontend rendering. ## D9 — Frontend stack: React + React Router + Tailwind v4 + Zustand **Decision:** The frontend (`apps/web`) uses React with React Router for routing, Tailwind CSS v4 for styling, and Zustand for state, built with Vite. **Context:** The frontend consumes the proxy API for search/fetch and `packages/extract` directly for analysis. React is the de-facto standard for this kind of tool; React Router provides declarative routes (`/` and `/mod/:id`); Tailwind v4 is the current major version and integrates via `@tailwindcss/vite`; Zustand is a minimal, unopinionated store that fits the small amount of client state (search + mod). **Alternatives considered:** Next.js (heavier than needed for a client-only tool); Redux Toolkit (more boilerplate than warranted); CSS Modules (no utility styling). ## D10 — Object inspection via a viewer registry **Decision:** The mod page shows a containment-tree sidebar (mirroring how TTS nests objects) and an inspector pane. The inspector resolves a per-class viewer from a registry (`registerViewer` / `resolveViewer`), falling back to a `DefaultViewer` that renders an object's raw fields. **Context:** The user wants to visualize objects in a save. A flat list of all objects and assets is hard to navigate, so the UI was reworked around a tree and a selected-object inspector. Custom viewers per object class (`Card`, `Bag`, ...) are expected later; a registry keeps that extension point explicit without coupling the page to any one viewer. **Alternatives considered:** A single monolithic inspector component. Rejected — would grow unboundedly as per-class viewers are added; the registry keeps each viewer isolated and swappable.