Files
tts-workshop/docs/bgm-loader-status.md
T
hypercross 15c45e7106 refactor: share trace, error boundary, and proxy http helpers
Lift the trace-to-shape geometry into @tts/mesh (traceToShape/
traceToUvBounds), parameterized by scale so the web token viewer and
@tts/tabletop share one implementation. Move the CORS proxy HTTP helpers
(assetUrl, resolveAssetUrl, traceImage) into a new @tts/http package, and
make @tts/tabletop's ErrorBoundary the single source used by the web app.

This removes the duplicated ErrorBoundary, assetUrl, tabletopHttp, and
trace-to-shape code from apps/web and packages/tabletop.
2026-08-09 22:17:24 +08:00

6.3 KiB
Raw Blame History

bgm Loader — Status

WIP. What's built, what works, what's missing, and the known issues. Spec: bgm-format.md.

What's built

packages/bgm — the loader core (new)

File Purpose
src/types.ts Roles (Package, Part, Surface, Setup, Route, Stacking, SurfaceMount, …), SerializedPackage (the JSON the plugin emits), BgmError, DefFile, ParsedDef
src/schemas.ts zod schemas + validate* for each role
src/markdown.ts Virtual def files from markdown code blocks, via marked. file= naming + content-hash auto-naming (./<hash>.yaml); multiple blocks may share a file= name
src/parse.ts yaml/json/toml → def objects (yaml, smol-toml); real-file walker (incl. .csv)
src/variants.ts $variants expansion via typed-csv (typed-csv/csv-loader). Inline (newline) vs path; paths resolve against the virtual def map
src/collect.ts loadDefs (real + virtual, virtual wins), collectPackages (package decl → include globs via picomatch → parts/surfaces/setups, type#id uniqueness, $variants on defs and route candidates). Sets each part's baseUrl to its source file's directory for resolving relative asset paths
src/vite.ts The vite plugin (bgm()): resolves virtual:bgm/packages (all packages) and virtual:bgm/package/<id> (one package) imports to export default <json>, watches source files for reload. Serializes the package's Maps to objects (SerializedPackage); throws on unknown packages; re-collects per load (no stale cache).
src/*.test.ts 23 tests, all passing — markdown extractor, typed-csv parsing (incl. the spec's empty-array + crop tuple cases), full harbor collection, plugin unit tests (resolve/load/shape/watch), and a real vite build integration test covering two packages

Deps: marked, typed-csv, yaml, smol-toml, picomatch, zod, vite, @types/picomatch.

games/harbor/harbor.md — example game (new)

Exercises the format end-to-end: package decl, two file=parts/tokens.yaml blocks, a table surface with mount/children and candidates: $variants against a virtual csv block, a child player surface, and a setup declaring its enabled surfaces. Same content duplicated as the vitest fixture under packages/bgm/src/__fixtures__/harbor/.

games/poker/poker.md — example game (new)

A real 52-card deck: a single card part expanded by $variants into 52 cards, each picking a cell from a 13×4 face sheet (cards-13x4.jpg) and a shared 4×1 back sheet (back-4x1.png). Assets are stored in Git LFS (.gitattributes).

packages/tabletop — rendering library (new)

A standalone r3f library that renders bgm parts. PartView/PartMesh build a mesh from a Part definition via @tts/mesh (size/fillet, face/back sprite UVs, traced shape or rect fallback). Proxy calls (/asset, /trace) default to @tts/http handlers and are overridable via TabletopProvider. Plan: bgm-tabletop-plan.md.

packages/http — shared proxy HTTP (new)

CORS-safe proxy HTTP helpers shared by the web app and @tts/tabletop: assetUrl/resolveAssetUrl (route external assets through /asset) and traceImage (image → vector shape via /trace, BSON-deserialized). The trace-to-shape geometry conversion lives in @tts/mesh (traceToShape/traceToUvBounds), and ErrorBoundary is shared via @tts/tabletop.

apps/proxy — local game assets (new)

The proxy serves local game assets from the games root: GAMES_ROOT (env or default) is set at startup (config.ts) and read by the /asset and /trace routes to serve relative paths (e.g. poker/parts/assets/cards.png) from disk, alongside the existing http(s) proxy path.

apps/web — consumer (new)

  • vite.config.ts — wired with bgm({ root: <repo>/games }), importing the plugin from @tts/bgm.
  • src/vite-env.d.ts — ambient declare module 'virtual:bgm/packages' (all packages) and 'virtual:bgm/package/*' (one package).
  • src/pages/BgmPage.tsx — list page: imports virtual:bgm/packages and shows every discovered package. Routed at /bgm.
  • src/pages/BgmPackagePage.tsx — detail page: looks up a package by id from bgm and renders its parts/surfaces/setups. Routed at /bgm/:id.

The vite plugin itself lives in packages/bgm/src/vite.ts (exported from @tts/bgm), not in the web app — so the loader's plugin is tested in isolation from the web project.

Works

  • pnpm --filter @tts/bgm build and typecheck pass.
  • Root pnpm test: 191 pass.
  • pnpm --filter @tts/web build succeeds; config warnings fixed.
  • src/vite.test.ts runs a real vite build against a self-contained fixture (src/__fixtures__/vite-build/) and asserts the bundled output contains the package data — the plugin is proven end-to-end without touching the web app.
  • pnpm --filter @tts/tabletop build / test pass; pnpm --filter @tts/proxy typecheck passes.

Known issues

  1. Plugin emits empty maps — fixed: load serializes Maps via Object.fromEntries.
  2. Plugin load uses this.error — fixed: throws instead.
  3. HMR cache not invalidated — fixed: dropped the closure cache; re-collects per load.
  4. tinyglobby leftover dep — removed.
  5. Package-level test script finds no files — added packages/bgm/vitest.config.ts.

Not yet done

  • No consumer yetapps/web/src/pages/BgmPage.tsx lists packages from virtual:bgm/packages; BgmPackagePage.tsx shows one at /bgm/:id.
  • $variants URL paths — spec mentions file/URL; URLs deferred.
  • zod SerializedPackage shape for the emitted JSON — the plugin emits SerializedPackage objects; a zod schema for the emitted module would give runtime validation beyond the ambient declare module.
  • setup value expansiontype without id → all parts of that type is documented but not implemented in the loader (it's a game-state init concern; noted as future).
  • Surface mounting is validated but not resolvedmount/children/surfaces are parsed and validated, but the loader doesn't resolve child→parent relationships or enforce that a setup's surfaces/a surface's children reference existing surfaces. That's a game-state/rendering concern (see docs/bgm-tabletop.md).
  • Docs for the loader itself (this file is the start).