"Shards of Dreams" is a TypeScript monorepo hosting a surreal 2D RPG MVP. The client is a Vite + React front end styled with TailwindCSS; the server is an Express API prepared for SQLite persistence via Drizzle ORM. Shared TypeScript contracts and JSON content power exploration, dialogue, combat, and progression systems across both layers.
client/ React front end (Vite, Tailwind, Zustand-free state)
src/app/ App shell and layout
src/features/ Feature slices (combat, dialogue, exploration, save)
src/state/ Central game state, content dictionaries
server/ Express server + Vite dev bridge
src/routes/ API endpoints (`/api/save`, `/api/content/:key`)
src/services/ Cross-route services (content cache, etc.)
src/db/ Drizzle schema + better-sqlite3 driver
shared/ Cross-cutting types and JSON content
Key gameplay content lives under shared/content/:
palaceFear.json– room graph for the Palace of Fear.dialogueBeach.json– branching Lister dialogue.enemies.json,skills.json– combat actors and abilities.
- Node.js 20 LTS (includes npm 10+).
- SQLite is bundled via
better-sqlite3; no external service is required.
npm installThis installs both runtime and development dependencies (including @types/better-sqlite3 for the server) and automatically bootstraps the Electron workspace under desktop/—even when installing with production flags—so commands such as npm run desktop:dev and npm run desktop:build have access to tools like concurrently and rimraf.
npm run checkRuns the TypeScript compiler (tsc) for the root project and then type-checks the Electron workspace under desktop/.
npm run devStarts the Express server (server/src/index.ts) via tsx. In development the server attaches Vite middleware, so the React client and API are served together on port 5000.
- Client entry: http://localhost:5000
- API endpoints: http://localhost:5000/api/*
The Express layer now includes structured request logging for all /api/* calls, trimming payload previews to 160 characters so terminal output remains readable during longer sessions.
npm run build- Builds the Vite client bundle into
dist/public. - Bundles the Express server with esbuild into
dist/server/index.js.
npm run startRuns the bundled Express server (node dist/server/index.js) which serves static assets from dist/public.
The Electron wrapper lives in the desktop/ workspace. It reuses the existing Vite renderer build and bundles dedicated main/preload processes.
npm run desktop:devRuns the Vite dev server alongside the Electron main process via concurrently. The command waits for the renderer (port 5173 by default) before launching Electron with TypeScript entry points.
npm run desktop:buildExecutes the full desktop pipeline: cleans previous artifacts, builds the renderer, compiles the main and preload scripts with tsc, and invokes electron-builder to produce distributables in dist/.
Verified with Node.js 20.18.0 / npm 10.8.2 on Linux x64 (electron-builder 24.13.3) via
npm run desktop:buildto confirm the release pipeline succeeds end-to-end after the dependency cleanup refactor.
npm run build:standalone:winGenerates a self-contained distribution for the current platform (defaults to Windows x64 when run via the npm script):
- Runs the standard production build if
dist/is missing. - Copies the client bundle, bundled server, SQLite database, and native
better_sqlite3.nodebinary into the standalone folder. - Uses
pkgto compiledist/server/index.jsinto a platform-specific executable targeting Node.js 18.
To build for a different target explicitly, run the script directly and supply --target <platform>-<arch>, for example node scripts/build-standalone.mjs --target win-x64 or --target linux-arm64. You can also pass --platform and --arch separately. Windows aliases such as win, win32, win64, and windows64 are all accepted and normalized to the supported win32 target.
The resulting folder contains the executable (DreamShards.exe on Windows, DreamShards elsewhere), the public/ assets directory, a server/dreamshards.db starter database, and the better_sqlite3.node native module required by SQLite. Ship or zip the entire folder to run the game without a separate Node.js installation.
- Main Menu – start a new dream or load the latest autosave.
- Dialogue – converse with Lister on the Dream Beach; choices toggle flags and unlock skills.
- Exploration – move through the Palace of Fear rooms, collect shards, trigger encounters, and initiate the boss gate.
- Combat – turn-based system with weakness bonuses, status effects (Vulnerable, Weakened, Guarded), skills, and items.
- Ending – post-boss epilogue reflects shard count and key decisions.
Auto-save runs whenever the game state changes outside the menu. Manual save/load buttons in the top bar call localStorage, and Push to Shore sends a snapshot to /api/save for future persistence work.
Auto-save runs whenever the game state changes outside the menu. Manual save/load buttons in the top bar call localStorage. The "Push to Shore" button currently restores the latest snapshot (with a confirmation when leaving an active run); wiring it to the remote save endpoint is still TODO.
- Schema:
server/src/db/schema.ts(savestable). - Config:
server/drizzle.config.ts(SQLite file stored atserver/dreamshards.dbby default). - Save endpoints are thin wrappers over Drizzle queries and accept an optional
limitquery string when listing saves (GET /api/save?limit=10). The limit is clamped between 1 and 25 and defaults to 5 to keep responses compact while still surfacing recent data quickly. - Shared content is streamed through
server/src/services/contentService.ts, which caches parsed JSON in memory and automatically invalidates cached entries when the underlying files change on disk. This keeps frequently requested resources (enemy stats, palace layouts, etc.) fast without sacrificing live-reload friendliness during development.
POST /api/save- Body:
{ state: AppState }. - Validation: The payload is checked against a
zodschema (server/src/schemas/appState.ts) so malformed snapshots are rejected with a 400 before they reach storage. - Response:
{ ok: true, id: number }.
- Body:
GET /api/save?limit=<1-25>- Optional
limitquery string controls how many records are returned (defaults to 5). - Response:
{ ok: true, saves: SaveRecord[] }.
- Optional
GET /api/save/:id- Response:
{ ok: true, save: SaveRecord }when found, otherwise{ ok: false, message: "not found" }with a 404.
- Response:
GET /api/content/:key- Serves JSON from
shared/content/<key>.jsonfor the allowed keysskills,enemies,palaceFear, anddialogueBeach. - Responses are cached in memory per file mtime so repeated requests avoid disk reads while editing content locally automatically busts the cache.
- Serves JSON from
- Drizzle commands (examples):
npx drizzle-kit generate npx drizzle-kit push
- TypeScript everywhere with strict mode enabled.
- TailwindCSS for styling; avoid bringing back the shadcn UI bundle removed during the rewrite.
- Shared types in
shared/types.tsdrive both runtime data and compile-time safety.
npm auditmay report moderate/high vulnerabilities from upstream packages (see install output); treat as a dependency hygiene backlog item.- UI still uses placeholder art hooks (
client/src/assets/README.md). - Remote persistence is not wired into the UI yet:
- Client has
pushSnapshotToServer()(client/src/features/save/saveSystem.ts). - Server accepts
POST /api/saveand supports listing/lookup (GET /api/save,GET /api/save/:id). - The top bar "Push to Shore" action currently restores the latest local snapshot instead of calling the API.
- Client has
Feel free to expand content JSON or add additional palaces by extending the shared schema and client state dictionaries.