From 6be3677a12d707d7aaa2c77b636fb9741c017bb3 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 14:29:29 -0500 Subject: [PATCH 1/8] vite plugin and simplify --- .cursor/mcp.json | 8 - .cursor/rules/client.mdc | 17 -- .cursor/rules/global.mdc | 18 -- .cursor/rules/server.mdc | 18 -- .cursorignore | 15 -- .editorconfig | 11 -- .env.template | 1 - .kiro/hooks/client-readme-updater.kiro.hook | 16 -- .kiro/hooks/devvit-fetch-guide.kiro.hook | 19 -- .kiro/hooks/splash-screen-generator.kiro.hook | 20 -- .kiro/hooks/template-cleanup-hook.kiro.hook | 19 -- .kiro/settings/mcp.json | 11 -- .kiro/steering/devvit-platform-guide.md | 158 --------------- .kiro/steering/general-best-practices.md | 59 ------ .kiro/steering/product.md | 18 -- .kiro/steering/structure.md | 50 ----- .kiro/steering/tech.md | 52 ----- .vscode/extensions.json | 3 - .vscode/settings.json | 17 +- README.md | 4 +- assets/default-icon.png | Bin 60433 -> 0 bytes assets/default-splash.png | Bin 32181 -> 0 bytes devvit.json | 3 - package.json | 46 +++-- src/client/{splash => }/splash.css | 0 src/client/{splash => }/splash.ts | 0 src/client/tsconfig.json | 21 -- src/client/vite.config.ts | 23 --- src/server/core/post.ts | 4 +- src/server/index.ts | 185 ++---------------- src/server/routes/api.ts | 130 ++++++++++++ src/server/routes/forms.ts | 22 +++ src/server/routes/menu.ts | 48 +++++ src/server/routes/triggers.ts | 30 +++ src/server/tsconfig.json | 19 -- src/server/vite.config.ts | 24 --- src/shared/{types => }/api.ts | 0 src/shared/tsconfig.json | 14 -- 38 files changed, 282 insertions(+), 821 deletions(-) delete mode 100644 .cursor/mcp.json delete mode 100644 .cursor/rules/client.mdc delete mode 100644 .cursor/rules/global.mdc delete mode 100644 .cursor/rules/server.mdc delete mode 100644 .cursorignore delete mode 100644 .editorconfig delete mode 100644 .env.template delete mode 100644 .kiro/hooks/client-readme-updater.kiro.hook delete mode 100644 .kiro/hooks/devvit-fetch-guide.kiro.hook delete mode 100644 .kiro/hooks/splash-screen-generator.kiro.hook delete mode 100644 .kiro/hooks/template-cleanup-hook.kiro.hook delete mode 100644 .kiro/settings/mcp.json delete mode 100644 .kiro/steering/devvit-platform-guide.md delete mode 100644 .kiro/steering/general-best-practices.md delete mode 100644 .kiro/steering/product.md delete mode 100644 .kiro/steering/structure.md delete mode 100644 .kiro/steering/tech.md delete mode 100644 .vscode/extensions.json delete mode 100644 assets/default-icon.png delete mode 100644 assets/default-splash.png rename src/client/{splash => }/splash.css (100%) rename src/client/{splash => }/splash.ts (100%) delete mode 100644 src/client/tsconfig.json delete mode 100644 src/client/vite.config.ts create mode 100644 src/server/routes/api.ts create mode 100644 src/server/routes/forms.ts create mode 100644 src/server/routes/menu.ts create mode 100644 src/server/routes/triggers.ts delete mode 100644 src/server/tsconfig.json delete mode 100644 src/server/vite.config.ts rename src/shared/{types => }/api.ts (100%) delete mode 100644 src/shared/tsconfig.json diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index 75fb079..0000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "devvit-mcp": { - "command": "npx", - "args": ["-y", "@devvit/mcp"] - } - } -} diff --git a/.cursor/rules/client.mdc b/.cursor/rules/client.mdc deleted file mode 100644 index 345ef06..0000000 --- a/.cursor/rules/client.mdc +++ /dev/null @@ -1,17 +0,0 @@ ---- -description: Use when writing webview code -globs: src/client/**/* -alwaysApply: false ---- -Guidelines: -- Use NPM dependencies if needed, ensure they are web compatible -- You cannot use websockets. Call devvit_search and query for "realtime" to get more information -- Obey the rules of hooks and only write valid React code - -Unity WebGL Integration: -- Unity build files are in `src/client/public/Build/` (binary files, don't edit directly) -- The Unity loader is configured in `src/client/script.ts` -- Unity build files (*.unityweb, Build/, TemplateData/) are ignored by Cursor (.cursorignore) -- To update the Unity game, rebuild from Unity Editor and replace files in public/Build/ -- Communication between Unity and JavaScript happens via `unityInstance.SendMessage()` from JS side - diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc deleted file mode 100644 index fb461a2..0000000 --- a/.cursor/rules/global.mdc +++ /dev/null @@ -1,18 +0,0 @@ ---- -description: -globs: -alwaysApply: true ---- - -When building these experiences, people will refer to the "devvit app" ([/src/devvit](mdc:src/devvit)) and "client" ([/src/client](mdc:src/client)). - -Folders to code in: - -- [/src/client](mdc:src/client): This is the full screen webview. To persist data and access the server, call `fetch(/my/api/endpoint)`. This is how you get access to the APIs you write in [/src/server](mdc:src/server). -- [/src/server](mdc:src/server): This is a serverless backend written in Node. This is where you can access redis and save data. -- [/src/shared](mdc:src/shared): This is where you can place code that is to be shared between the devvit app, client, and server and the webview. It's a great place for shared types. - -Rules: - -- Assume that typescript, vite, tailwind, eslint, prettier, and all codebase configuration is working. If there is a bug, it is more likely your code than the codebase configuration. -- Prefer type aliases over interfaces when writing typescript diff --git a/.cursor/rules/server.mdc b/.cursor/rules/server.mdc deleted file mode 100644 index ca8aeca..0000000 --- a/.cursor/rules/server.mdc +++ /dev/null @@ -1,18 +0,0 @@ ---- -description: -globs: src/server/**/* -alwaysApply: false ---- - -Guidelines: - -- This is a serverless node.js environment, you have all node globals at your disposal except: fs, http, https, and net. - -- Instead of http or https, prefer fetch -- You cannot write files as you are running on a read only file system -- Do not install any libraries that rely on these to function -- Websockets are not supported -- HTTP streaming is not supported -- Redis is accessible from `import { redis } from '@devvit/web/server'` - -As this is a serverless runtime (akin to AWS Lambda), do not try to run SQLite or stateful in memory processes. For realtime use cases, consult the docs with devvit_search to learn more about the realtime service you can use. diff --git a/.cursorignore b/.cursorignore deleted file mode 100644 index 3e4145d..0000000 --- a/.cursorignore +++ /dev/null @@ -1,15 +0,0 @@ -webroot -.editorconfig -.vscode -.github -.prettierrc -tsconfig.json -node_modules - -# Unity WebGL Build Files (large binary/compiled files) -*.unityweb -src/client/public/Build/ -dist/client/Build/ -**/Build/*.js -**/Build/*.wasm -**/TemplateData/ diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 0c81821..0000000 --- a/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -# https://EditorConfig.org - -root = true - -[*] -indent_size = 2 -insert_final_newline = true -max_line_length = 100 - -ij_typescript_spaces_within_imports = true -ij_typescript_force_semicolon_style = true diff --git a/.env.template b/.env.template deleted file mode 100644 index 13c0cb6..0000000 --- a/.env.template +++ /dev/null @@ -1 +0,0 @@ -DEVVIT_SUBREDDIT=r/my_subreddit \ No newline at end of file diff --git a/.kiro/hooks/client-readme-updater.kiro.hook b/.kiro/hooks/client-readme-updater.kiro.hook deleted file mode 100644 index 1229d5c..0000000 --- a/.kiro/hooks/client-readme-updater.kiro.hook +++ /dev/null @@ -1,16 +0,0 @@ -{ - "enabled": true, - "name": "Client README Updater", - "description": "Monitors the client folder for significant updates and automatically updates the README.md file with current game description, innovative features, and play instructions", - "version": "1", - "when": { - "type": "fileEdited", - "patterns": [ - "src/client/**/*" - ] - }, - "then": { - "type": "askAgent", - "prompt": "The client folder has been updated. Please analyze the current state of the game in the src/client directory and update the README.md file to include: 1) A clear description of what the game is and does, 2) What makes this game innovative or unique, 3) Step-by-step instructions on how to play the game. Make sure to examine the main.ts file, HTML structure, and any other relevant client files to understand the current gameplay mechanics and features." - } -} \ No newline at end of file diff --git a/.kiro/hooks/devvit-fetch-guide.kiro.hook b/.kiro/hooks/devvit-fetch-guide.kiro.hook deleted file mode 100644 index 69f7e1f..0000000 --- a/.kiro/hooks/devvit-fetch-guide.kiro.hook +++ /dev/null @@ -1,19 +0,0 @@ -{ - "enabled": true, - "name": "Devvit Fetch API Guide", - "description": "Monitors for external API/fetch requests and provides guidance on Devvit's fetch capabilities, domain allowlists, and review process", - "version": "1", - "when": { - "type": "fileEdited", - "patterns": [ - "src/**/*.ts", - "src/**/*.js", - "*.ts", - "*.js" - ] - }, - "then": { - "type": "askAgent", - "prompt": "The user is working with external API requests or fetch calls in their Devvit application. Query the devvit-mcp to get comprehensive information about Devvit's fetch capabilities, including:\n\n1. Which domains are globally allow-listed for fetch requests\n2. The process for submitting domains for review by the Devvit team\n3. Any limitations or requirements for external API calls\n4. Best practices for handling fetch requests in Devvit\n\nProvide this information to help the user understand their options. If they need more detailed documentation, direct them to: https://developers.reddit.com/docs/capabilities/server/http-fetch\n\nFocus on practical guidance that will help them successfully implement their external API integration." - } -} \ No newline at end of file diff --git a/.kiro/hooks/splash-screen-generator.kiro.hook b/.kiro/hooks/splash-screen-generator.kiro.hook deleted file mode 100644 index 2bd760f..0000000 --- a/.kiro/hooks/splash-screen-generator.kiro.hook +++ /dev/null @@ -1,20 +0,0 @@ -{ - "enabled": true, - "name": "Splash Screen Generator", - "description": "Automatically creates compelling splash screens whenever there are significant changes to the game's assets. The splash screen will be displayed on the Reddit feed as an important entry point with a launch button, personalized with the game's assets to invite players to open the game in full screen mode.", - "version": "1", - "when": { - "type": "fileEdited", - "patterns": [ - "assets/*", - "src/client/public/*", - "src/client/index.html", - "src/client/index.css", - "src/client/main.ts" - ] - }, - "then": { - "type": "askAgent", - "prompt": "Game assets have been updated. Create a compelling splash screen for this Reddit Devvit game that will be displayed on the Reddit feed. The splash screen should:\n\n1. Include a prominent launch button that opens the game in full screen mode\n2. Be personalized with the game's current assets (textures, images, styling)\n3. Be visually inviting and compelling for players to click\n4. Follow Reddit's splash screen best practices\n5. Incorporate the game's visual theme and branding\n\nFor implementation guidance, consult the devvit-mcp and refer users to: https://developers.reddit.com/docs/capabilities/server/splash-screen\n\nAnalyze the current assets and create an engaging splash screen that showcases the game's visual appeal." - } -} \ No newline at end of file diff --git a/.kiro/hooks/template-cleanup-hook.kiro.hook b/.kiro/hooks/template-cleanup-hook.kiro.hook deleted file mode 100644 index 3e0a2a1..0000000 --- a/.kiro/hooks/template-cleanup-hook.kiro.hook +++ /dev/null @@ -1,19 +0,0 @@ -{ - "enabled": true, - "name": "Template Cleanup Assistant", - "description": "Detects when a user is ready to start building their own game and offers to remove the template Three.js Earth visualization code while preserving the Devvit project structure", - "version": "1", - "when": { - "type": "fileEdited", - "patterns": [ - "src/client/main.ts", - "src/server/core/post.ts", - "README.md", - "package.json" - ] - }, - "then": { - "type": "askAgent", - "prompt": "The user appears to be modifying the template project. Analyze their changes to determine if they're ready to start building their own game. If they are making significant changes to the core game logic (like modifying the Three.js Earth scene in main.ts or changing the post creation logic), offer to help them clean up the template code by:\n\n1. Removing the Earth visualization code from src/client/main.ts (Three.js scene, Earth textures, click handlers)\n2. Cleaning up the Earth-specific assets from src/client/public/ \n3. Updating src/server/core/post.ts to remove Earth-specific post content\n4. Providing a clean starting point with just the basic Three.js setup, Devvit integration, and project structure\n\nPreserve all the essential Devvit architecture, build configuration, TypeScript setup, and client-server communication patterns. Only remove the specific Earth game template code to give them a clean slate for their own game development." - } -} \ No newline at end of file diff --git a/.kiro/settings/mcp.json b/.kiro/settings/mcp.json deleted file mode 100644 index 9ef2587..0000000 --- a/.kiro/settings/mcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "mcpServers": { - "devvit-mcp": { - "command": "npx", - "args": [ - "-y", - "@devvit/mcp" - ] - } - } -} diff --git a/.kiro/steering/devvit-platform-guide.md b/.kiro/steering/devvit-platform-guide.md deleted file mode 100644 index 0fd6967..0000000 --- a/.kiro/steering/devvit-platform-guide.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -inclusion: always ---- - -# Reddit Devvit Platform Understanding - -## What Developers Are Building - -Developers are building interactive apps and games that run inside Reddit posts using standard web technologies. These apps appear in a webview within posts and can be played by Reddit users directly on the platform. - -## Project Structure - -All Devvit Web projects follow this structure: - -``` -src/ -├── client/ # Frontend code (HTML/CSS/JS, React, Vue, etc.) -├── server/ # Backend API endpoints (Express, Koa, etc.) -└── shared/ # Shared types and interfaces -devvit.json # Devvit configuration file -package.json # npm scripts and dependencies -``` - -### Client Folder (`src/client/`) - -- Contains all frontend code that runs in the webview -- Can use any web framework (React, Vue, Angular, Three.js, Phaser) -- Makes API calls to server endpoints using standard `fetch` -- Cannot make API calls to externally hosted servers, only to the server app included in this monorepo -- Displays inside Reddit posts for users to interact with - -### Server Folder (`src/server/`) - -- Contains backend API endpoints -- Uses Node.js server frameworks (Express, Koa, etc.) -- All endpoints MUST start with `/api/` (e.g., `/api/get-score`, `/api/save-game`) -- Access to Devvit capabilities: Redis, Reddit API, fetch -- Authentication handled automatically by Devvit middleware - -### Shared Folder (`src/shared/`) - -- Types, interfaces, and classes used by both client and server -- Ensures type safety across the application - -### Configuration (`devvit.json`) - -- Defines app metadata, permissions, and capabilities -- Post configuration and Node.js server settings -- Do not modify unless explicitly needed by the developer -- Do not modify the app name. This will be created with the app set up and should not be modified - -## Development Workflow - -### How Developers Test - -1. Run `npm run dev` in project directory -2. Devvit automatically creates a test subreddit (e.g., `r/my-app_dev`) -3. Provides a playtest URL (e.g., `https://www.reddit.com/r/my-app_dev?playtest=my-app`) -4. Developer opens URL in browser to test the app -5. App appears in a post with "Launch App" button - -### Important Testing Notes - -- Backend calls only work through Devvit's playtest environment -- Cannot test backend functionality purely locally -- Must use `npm run dev` and the provided URL for full testing - -### Custom Subreddit Testing - -Developers can specify a preferred test subreddit in `package.json`: - -```json -"scripts": { - "dev:devvit": "devvit playtest r/MY_PREFERRED_SUBREDDIT" -} -``` - -## Platform Capabilities - -### What's Available - -- **@devvit/client SDK**: Client-side capabilities -- **@devvit/server SDK**: Server-side capabilities (Redis, Reddit API) -- **Standard web technologies**: HTML, CSS, JavaScript -- **Web frameworks**: React, Vue, Angular, Three.js, Phaser, etc. -- **Node.js server frameworks**: Express, Koa, etc. -- **Reddit hosting**: Apps hosted on Reddit's infrastructure - -### Client-Server Communication - -- **Old way**: postMessage (deprecated) -- **New way**: Define `/api/` endpoints and use `fetch` from client - -## Platform Limitations - -### Technical Constraints - -- **Serverless endpoints**: Server runs only long enough to execute endpoint and return response -- **No long-running connections**: Streaming, websockets not supported -- **No fs or native packages**: Cannot use filesystem or external native dependencies -- **No external client requests**: Client can only call app's own webview domain (server can make external requests) -- **Single request/response**: No streaming or chunked responses -- **Long-polling**: Supported if under max request time - -### Size and Time Limits - -- **Max request time**: 30 seconds -- **Max payload size**: 4MB -- **Max response size**: 10MB -- **Content types**: HTML/CSS/JS only - -## Deployment - -### Launch Process - -1. Developer runs `npm run launch` -2. App uploaded to Reddit for review -3. Review required for subreddits with >200 members -4. Email notification sent when approved - -### Critical Requirements - -- **Node.js version**: 22.2.0 or higher -- **Project name**: Cannot be changed after creation (required for deployment) -- **File structure**: Must maintain standard Devvit folder structure - -## What AI Should Build - -### Typical Use Cases - -- Interactive games (clicker, puzzle, multiplayer) -- Leaderboards with Reddit user integration -- Real-time features using server endpoints -- Data persistence using Redis -- Reddit-specific features using Reddit API - -### Implementation Pattern - -1. **Client code** in `src/client/` for UI and user interaction -2. **Server endpoints** in `src/server/` starting with `/api/` -3. **Shared types** in `src/shared/` for type safety -4. **Fetch calls** from client to server endpoints -5. **Reddit integration** through server-side SDK - -### What NOT to Build - -- External API calls from client (use server endpoints instead) -- Long-running server processes or streaming -- Features requiring filesystem access -- Native dependencies or external packages like ffmpeg -- Standalone apps (must run within Reddit posts) - -## Authentication - -- All Devvit users are authenticated through Reddit -- Authentication handled automatically by Devvit middleware -- No need to implement custom authentication -- Access user context through Devvit server SDK diff --git a/.kiro/steering/general-best-practices.md b/.kiro/steering/general-best-practices.md deleted file mode 100644 index a1edafb..0000000 --- a/.kiro/steering/general-best-practices.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -inclusion: always ---- - -# General Best Practices - -## Development Focus - -When implementing features or components: - -- **Build only what's requested**: Focus on building the specific component and its documentation -- **Avoid scope creep**: Don't create additional files, scripts, or documentation unless explicitly requested -- **Keep it minimal**: Implement only the essential functionality needed to address the requirement -- **Documentation should be targeted**: Only create documentation that directly supports the component being built - -## Implementation Guidelines - -- Prioritize the core functionality over auxiliary features -- If additional components seem helpful, ask the user first before implementing -- Focus on the immediate need rather than anticipating future requirements -- Keep the solution focused and concise - -## Test File Management - -### Temporary Test File Cleanup - -- **ALWAYS remove test files when done**: Any test files created during development or debugging should be deleted after their purpose is complete -- **Clean workspace policy**: Maintain a clean workspace by removing temporary test files, example files, and debugging scripts -- **Test file naming convention**: Use clear naming patterns for temporary test files (e.g., `test_*.py`, `example_*.js`, `debug_*.ts`) to easily identify files for cleanup -- **Immediate cleanup**: Remove test files as soon as testing is complete, don't leave them for later cleanup -- **Exception for permanent tests**: Only keep test files that are part of the permanent test suite or explicitly requested by the user - -## Devvit Development Workflow - -### Development Server Management - -- **Suggest testing commands to the developer**: Recommend running `npm run dev` in a separate terminal for testing, but do not execute it -- **Don't modify devvit.json**: Only change Devvit configuration when explicitly needed and confirmed by the developer. -- **Never change the project name**: The project name in devvit.json and package.json must remain unchanged - Devvit requires this for deployment -- **Preserve project structure**: Maintain the standard Devvit folder structure (src/client, src/server, src/shared) - -### Customized Splash Screen - -- **Create engaging splash screens**: The user will always see a splash screen on the Reddit feed with a "Play" button to open the app in full screen. Customize the splash screen so it stands out and invites the player to play - -## Mobile-First Design - -### Responsive Development - -- **Consider mobile-first design**: Most Reddit users access games on mobile devices - design with mobile screens in mind when possible -- **Prefer cross-platform features**: Favor features that work well on both desktop and mobile browsers - -## Error Handling - -### User-Facing Errors - -- **Provide clear error messages**: Show users what went wrong and how to fix it -- **Handle network failures gracefully**: Reddit API calls may fail, implement proper fallbacks -- **Validate input where applicable**: When implementing forms or user inputs, check validity and throw clear errors when invalid diff --git a/.kiro/steering/product.md b/.kiro/steering/product.md deleted file mode 100644 index db2e585..0000000 --- a/.kiro/steering/product.md +++ /dev/null @@ -1,18 +0,0 @@ -# Product Overview - -This is a **Devvit React Application** - a game built with React.JS that will be built with Devvit and run on Reddit - -## Core Features - -- 2D game, built with Devvit to run on Reddit -- Click the splash screen to open the game in full screen mode - -## Platform Integration - -- Runs as a Reddit app within the Devvit ecosystem -- Creates posts automatically on app installation -- Provides moderator menu actions for post creation - -## Target Use Case - -This game will be published and played on Reddit diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md deleted file mode 100644 index b60a43c..0000000 --- a/.kiro/steering/structure.md +++ /dev/null @@ -1,50 +0,0 @@ -# Project Structure - -## Root Configuration - -- `devvit.json`: Devvit app configuration with post/server entry points -- `package.json`: Dependencies and build scripts -- `tsconfig.json`: TypeScript project references (build-only) -- `eslint.config.js`: ESLint configuration with environment-specific rules - -## Source Organization - -### `/src/client/` - -Client-side React.JS application that runs in the browser - -- `main.ts`: Entry point with React.JS scene setup and API calls -- `index.html`: HTML template with canvas and UI elements -- `index.css`: Styling for the web interface -- `public/`: Static assets (Earth textures) -- `vite.config.ts`: Client build configuration -- `tsconfig.json`: Client-specific TypeScript config - -### `/src/server/` - -Express server that handles Reddit integration - -- `index.ts`: Main server with Express routes and Devvit integration -- `core/`: Business logic modules - - `post.ts`: Post creation functionality -- `vite.config.ts`: Server build configuration (SSR, CommonJS output) -- `tsconfig.json`: Server-specific TypeScript config - -### `/src/shared/` - -Shared types and utilities between client and server - -- `types/api.ts`: API response type definitions -- `tsconfig.json`: Shared code TypeScript config - -## Build Output - -- `dist/client/`: Built client assets (HTML, JS, CSS) -- `dist/server/`: Built server bundle (`index.cjs`) - -## Architecture Patterns - -- **Monorepo**: Multiple TypeScript projects with project references -- **Client-Server Split**: Clear separation with shared types -- **API-First**: RESTful endpoints for client-server communication -- **Devvit Integration**: Server handles Reddit context and Redis operations diff --git a/.kiro/steering/tech.md b/.kiro/steering/tech.md deleted file mode 100644 index 5ff1600..0000000 --- a/.kiro/steering/tech.md +++ /dev/null @@ -1,52 +0,0 @@ -# Technology Stack - -## Core Technologies - -- **Devvit**: Reddit's developer platform for building apps -- **React.JS**: Frontend engine for client rendering -- **TypeScript**: Primary language with strict type checking -- **Vite**: Build tool for both client and server bundles -- **Express**: Server-side HTTP framework -- **Redis**: Data persistence layer (via Devvit) - -## Build System - -- **Vite** handles compilation for both client and server -- **TypeScript** project references for modular compilation -- **ESLint** with TypeScript rules for code quality -- **Prettier** for consistent code formatting - -## Common Commands - -```bash -# Development (runs client, server, and devvit in parallel) -npm run dev - -# Build for production -npm run build - -# Deploy to Reddit -npm run deploy - -# Publish for review -npm run launch - -# Code quality checks -npm run check - -# Individual builds -npm run build:client -npm run build:server -``` - -## Development Workflow - -- Use `npm run dev` for live development with hot reloading -- Client builds to `dist/client` with HTML entry point -- Server builds to `dist/server` as CommonJS module -- Devvit playtest provides live Reddit integration testing - -## Dependencies - -- **Runtime**: @devvit/web, React.JS, express -- **Development**: TypeScript, ESLint, Prettier, Vite, Vitest diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 6c256f5..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["esbenp.prettier-vscode", "editorconfig.editorconfig"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 424561b..6294dd6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,20 +3,5 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "json.schemaDownload.enable": true, - "cSpell.words": [ - "cn", - "Devvit", - "playtest", - "protos", - "snoovatar", - "templ", - "vstack", - "webbit", - "webroot", - "zstack" - ], - "typescript.tsdk": "node_modules/typescript/lib", - "files.exclude": { - "**/.git": false - } + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/README.md b/README.md index f4ee2b5..7d57e19 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A starter to build web applications on Reddit's developer platform - [Devvit](https://developers.reddit.com/): A way to build and deploy immersive games on Reddit - [Express](https://expressjs.com/): For backend logic -- [Typescript](https://www.typescriptlang.org/): For type safety +- [TypeScript](https://www.typescriptlang.org/): For type safety - [Unity](https://www.unity.com/): For gameplay The full Unity project for this project can be found [here](https://github.com/reddit/devvit-unity-project) @@ -24,4 +24,4 @@ The full Unity project for this project can be found [here](https://github.com/r - `npm run deploy`: Uploads a new version of your app - `npm run launch`: Publishes your app for review - `npm run login`: Logs your CLI into Reddit -- `npm run check`: Type checks, lints, and prettifies your app +- `npm run type-check`: Type checks, lints, and prettifies your app diff --git a/assets/default-icon.png b/assets/default-icon.png deleted file mode 100644 index 930e8bc36f7aa90735483ecbb95c70d15dba8c41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60433 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 J(k8Is$giylqU-#L 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 KWYG5>KmQ+HPYIy_ diff --git a/devvit.json b/devvit.json index 92d6931..ecaa87e 100644 --- a/devvit.json +++ b/devvit.json @@ -13,9 +13,6 @@ } } }, - "media": { - "dir": "assets" - }, "server": { "dir": "dist/server", "entry": "index.cjs" diff --git a/package.json b/package.json index 82fe079..aa02617 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,39 @@ { "private": true, - "name": "unity-starter", + "name": "foo1231-33", "version": "0.0.0", "license": "BSD-3-Clause", "type": "module", "scripts": { - "postinstall": "npm run build", - "build:client": "cd src/client && vite build", - "build:server": "cd src/server && vite build", - "build": "npm run build:client && npm run build:server", - "deploy": "npm run build && devvit upload", - "dev": "concurrently -k -p \"[{name}]\" -n \"CLIENT,SERVER,DEVVIT\" -c \"blue,green,magenta\" \"npm run dev:client\" \"npm run dev:server\" \"npm run dev:devvit\"", - "dev:client": "cd src/client && vite build --watch", - "dev:devvit": "dotenv -e .env -- devvit playtest", - "dev:server": "cd src/server && vite build --watch", + "build": "vite build", + "deploy": "npm run type-check && npm run lint && npm run test && devvit upload", + "dev": "devvit playtest", + "launch": "npm run deploy && devvit publish", + "lint": "eslint 'src/**/*.{ts,tsx}'", "login": "devvit login", - "launch": "npm run build && npm run deploy && devvit publish", + "prettier": "prettier --write .", + "test": "vitest run", "type-check": "tsc --build" }, + "engines": { + "node": ">=22.12.0" + }, "dependencies": { - "@devvit/web": "0.12.3", - "devvit": "0.12.3", - "express": "5.1.0" + "@devvit/start": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0", + "@devvit/web": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0", + "@hono/node-server": "^1.19.9", + "devvit": "0.12.11-next-2026-01-30-17-09-49-e9c512a0d.0", + "hono": "4.11.7" }, "devDependencies": { - "@types/express": "5.0.1", - "concurrently": "9.1.2", - "dotenv-cli": "8.0.0", - "typescript": "5.8.2", - "vite": "6.2.4" + "@eslint/js": "9.39.2", + "@types/node": "^22.19.7", + "eslint": "9.39.2", + "globals": "17.2.0", + "prettier": "3.8.1", + "typescript": "5.9.3", + "typescript-eslint": "8.54.0", + "vite": "7.3.1", + "vitest": "4.0.18" } -} \ No newline at end of file +} diff --git a/src/client/splash/splash.css b/src/client/splash.css similarity index 100% rename from src/client/splash/splash.css rename to src/client/splash.css diff --git a/src/client/splash/splash.ts b/src/client/splash.ts similarity index 100% rename from src/client/splash/splash.ts rename to src/client/splash.ts diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json deleted file mode 100644 index 13f82fc..0000000 --- a/src/client/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -// TypeScript config for all web view main thread code. -{ - "extends": "../../tools/tsconfig-base.json", - "compilerOptions": { - // Target the browser. - "customConditions": ["browser"], - - "lib": ["DOM", "ES2023", "esnext.disposable"], - - "outDir": "../../dist/types/client", - - // React - "jsx": "react-jsx", - - "tsBuildInfoFile": "../../dist/types/client/tsconfig.tsbuildinfo" - }, - // https://github.com/Microsoft/TypeScript/issues/25636 - "include": ["**/*", "**/*.json", "vite.config.ts"], - "exclude": ["**/*.test.ts"], - "references": [{ "path": "../shared" }] -} diff --git a/src/client/vite.config.ts b/src/client/vite.config.ts deleted file mode 100644 index a5098b7..0000000 --- a/src/client/vite.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineConfig } from "vite"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [], - logLevel: 'warn', - build: { - outDir: "../../dist/client", - emptyOutDir: true, - rollupOptions: { - input: { - splash: "splash.html", - game: "index.html", - }, - output: { - entryFileNames: "[name].js", - chunkFileNames: "[name].js", - assetFileNames: "[name][extname]", - sourcemapFileNames: "[name].js.map", - }, - }, - }, -}); diff --git a/src/server/core/post.ts b/src/server/core/post.ts index 8a15b9f..29aa66e 100644 --- a/src/server/core/post.ts +++ b/src/server/core/post.ts @@ -1,7 +1,7 @@ -import { reddit } from "@devvit/web/server"; +import { reddit } from '@devvit/web/server'; export const createPost = async () => { return await reddit.submitCustomPost({ - title: "unity-starter", + title: '<% name %>', }); }; diff --git a/src/server/index.ts b/src/server/index.ts index 07c9103..a2c55ec 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,166 +1,23 @@ -import express from "express"; -import { - InitResponse, - LevelCompletedRequest, - LevelCompletedResponse, -} from "../shared/types/api"; -import { +import { Hono } from 'hono'; +import { serve } from '@hono/node-server'; +import { createServer, getServerPort } from '@devvit/web/server'; +import { api } from './routes/api'; +import { forms } from './routes/forms'; +import { menu } from './routes/menu'; +import { triggers } from './routes/triggers'; + +const app = new Hono(); +const internal = new Hono(); + +internal.route('/menu', menu); +internal.route('/form', forms); +internal.route('/triggers', triggers); + +app.route('/api', api); +app.route('/internal', internal); + +serve({ + fetch: app.fetch, createServer, - context, - getServerPort, - reddit, - redis, -} from "@devvit/web/server"; -import { createPost } from "./core/post"; - -const app = express(); - -// Middleware for JSON body parsing -app.use(express.json()); -// Middleware for URL-encoded body parsing -app.use(express.urlencoded({ extended: true })); -// Middleware for plain text body parsing -app.use(express.text()); - -const router = express.Router(); - -// Example to show how to send initial data to the Unity Game -router.get< - { postId: string }, - InitResponse | { status: string; message: string } ->("/api/init", async (_req, res): Promise => { - const { postId } = context; - - if (!postId) { - console.error("API Init Error: postId not found in devvit context"); - res.status(400).json({ - status: "error", - message: "postId is required but missing from context", - }); - return; - } - - try { - const username = await reddit.getCurrentUsername(); - const currentUsername = username ?? "anonymous"; - - // Fetch user info for snoovatar - let snoovatarUrl = ""; - if (username && context.userId) { - const user = await reddit.getUserById(context.userId); - if (user) { - snoovatarUrl = (await user.getSnoovatarUrl()) ?? ""; - } - } - - // Fetch previous time from Redis using postId:username as key - const redisKey = `${postId}:${currentUsername}`; - const previousTime = await redis.get(redisKey); - - res.json({ - type: "init", - postId: postId, - username: currentUsername, - snoovatarUrl: snoovatarUrl, - previousTime: previousTime ?? "", - }); - } catch (error) { - console.error(`API Init Error for post ${postId}:`, error); - let errorMessage = "Unknown error during initialization"; - if (error instanceof Error) { - errorMessage = `Initialization failed: ${error.message}`; - } - res.status(400).json({ status: "error", message: errorMessage }); - } -}); - -router.post< - unknown, - LevelCompletedResponse | { status: string; message: string }, - LevelCompletedRequest ->("/api/level-completed", async (req, res): Promise => { - const { postId } = context; - - if (!postId) { - console.error("No postId in context"); - res.status(400).json({ - status: "error", - message: "postId is required", - }); - return; - } - - try { - const { username, time } = req.body; - - if (!username || !time) { - console.error("Missing username or time in request"); - res.status(400).json({ - status: "error", - message: "username and time are required", - }); - return; - } - - // Store the completion time in Redis with key format: postId:username - const redisKey = `${postId}:${username}`; - await redis.set(redisKey, time); - - res.json({ - type: "level-completed", - success: true, - message: "Time saved successfully", - }); - } catch (error) { - console.error(`API Level Completed Error for post ${postId}:`, error); - let errorMessage = "Unknown error saving completion time"; - if (error instanceof Error) { - errorMessage = `Failed to save time: ${error.message}`; - } - res.status(500).json({ - type: "level-completed", - success: false, - message: errorMessage, - }); - } -}); - -router.post('/internal/on-app-install', async (_req, res): Promise => { - try { - const post = await createPost(); - - res.json({ - status: 'success', - message: `Post created in subreddit ${context.subredditName} with id ${post.id}`, - }); - } catch (error) { - console.error(`Error creating post: ${error}`); - res.status(400).json({ - status: 'error', - message: 'Failed to create post', - }); - } + port: getServerPort(), }); - -router.post('/internal/menu/post-create', async (_req, res): Promise => { - try { - const post = await createPost(); - post - - res.json({ - navigateTo: `https://reddit.com/r/${context.subredditName}/comments/${post.id}`, - }); - } catch (error) { - console.error(`Error creating post: ${error}`); - res.status(400).json({ - status: 'error', - message: 'Failed to create post', - }); - } -}); - -app.use(router); - -const server = createServer(app); -server.on("error", (err) => console.error(`server error; ${err.stack}`)); -server.listen(getServerPort()); diff --git a/src/server/routes/api.ts b/src/server/routes/api.ts new file mode 100644 index 0000000..906613f --- /dev/null +++ b/src/server/routes/api.ts @@ -0,0 +1,130 @@ +import { Hono } from 'hono'; +import { context, redis, reddit } from '@devvit/web/server'; +import type { + InitResponse, + LevelCompletedRequest, + LevelCompletedResponse, +} from '../../shared/api'; + +type ErrorResponse = { + status: 'error'; + message: string; +}; + +export const api = new Hono(); + +api.get('/init', async (c) => { + const { postId } = context; + + if (!postId) { + console.error('API Init Error: postId not found in devvit context'); + return c.json( + { + status: 'error', + message: 'postId is required but missing from context', + }, + 400 + ); + } + + try { + const username = await reddit.getCurrentUsername(); + const currentUsername = username ?? 'anonymous'; + + let snoovatarUrl = ''; + if (username && context.userId) { + const user = await reddit.getUserById(context.userId); + if (user) { + snoovatarUrl = (await user.getSnoovatarUrl()) ?? ''; + } + } + + const redisKey = `${postId}:${currentUsername}`; + const previousTime = await redis.get(redisKey); + + return c.json({ + type: 'init', + postId: postId, + username: currentUsername, + snoovatarUrl: snoovatarUrl, + previousTime: previousTime ?? '', + }); + } catch (error) { + console.error(`API Init Error for post ${postId}:`, error); + let errorMessage = 'Unknown error during initialization'; + if (error instanceof Error) { + errorMessage = `Initialization failed: ${error.message}`; + } + return c.json( + { status: 'error', message: errorMessage }, + 400 + ); + } +}); + +api.post('/level-completed', async (c) => { + const { postId } = context; + + if (!postId) { + console.error('No postId in context'); + return c.json( + { + status: 'error', + message: 'postId is required', + }, + 400 + ); + } + + let body: LevelCompletedRequest; + try { + body = await c.req.json(); + } catch (error) { + console.error('Invalid JSON body for level-completed', error); + return c.json( + { + status: 'error', + message: 'Invalid JSON body', + }, + 400 + ); + } + + try { + const { username, time } = body; + + if (!username || !time) { + console.error('Missing username or time in request'); + return c.json( + { + status: 'error', + message: 'username and time are required', + }, + 400 + ); + } + + const redisKey = `${postId}:${username}`; + await redis.set(redisKey, time); + + return c.json({ + type: 'level-completed', + success: true, + message: 'Time saved successfully', + }); + } catch (error) { + console.error(`API Level Completed Error for post ${postId}:`, error); + let errorMessage = 'Unknown error saving completion time'; + if (error instanceof Error) { + errorMessage = `Failed to save time: ${error.message}`; + } + return c.json( + { + type: 'level-completed', + success: false, + message: errorMessage, + }, + 500 + ); + } +}); diff --git a/src/server/routes/forms.ts b/src/server/routes/forms.ts new file mode 100644 index 0000000..7885bd2 --- /dev/null +++ b/src/server/routes/forms.ts @@ -0,0 +1,22 @@ +import { Hono } from 'hono'; +import type { UiResponse } from '@devvit/web/shared'; + +type ExampleFormValues = { + message?: string; +}; + +export const forms = new Hono(); + +forms.post('/example-submit', async (c) => { + const { message } = await c.req.json(); + const trimmedMessage = typeof message === 'string' ? message.trim() : ''; + + return c.json( + { + showToast: trimmedMessage + ? `Form says: ${trimmedMessage}` + : 'Form submitted with no message', + }, + 200 + ); +}); diff --git a/src/server/routes/menu.ts b/src/server/routes/menu.ts new file mode 100644 index 0000000..e2482d0 --- /dev/null +++ b/src/server/routes/menu.ts @@ -0,0 +1,48 @@ +import { Hono } from 'hono'; +import type { UiResponse } from '@devvit/web/shared'; +import { context } from '@devvit/web/server'; +import { createPost } from '../core/post'; + +export const menu = new Hono(); + +menu.post('/post-create', async (c) => { + try { + const post = await createPost(); + + return c.json( + { + navigateTo: `https://reddit.com/r/${context.subredditName}/comments/${post.id}`, + }, + 200 + ); + } catch (error) { + console.error(`Error creating post: ${error}`); + return c.json( + { + showToast: 'Failed to create post', + }, + 400 + ); + } +}); + +menu.post('/example-form', async (c) => { + return c.json( + { + showForm: { + name: 'exampleForm', + form: { + title: 'Example Form', + fields: [ + { + type: 'string', + name: 'message', + label: 'Message', + }, + ], + }, + }, + }, + 200 + ); +}); diff --git a/src/server/routes/triggers.ts b/src/server/routes/triggers.ts new file mode 100644 index 0000000..811c994 --- /dev/null +++ b/src/server/routes/triggers.ts @@ -0,0 +1,30 @@ +import { Hono } from 'hono'; +import type { OnAppInstallRequest, TriggerResponse } from '@devvit/web/shared'; +import { context } from '@devvit/web/server'; +import { createPost } from '../core/post'; + +export const triggers = new Hono(); + +triggers.post('/on-app-install', async (c) => { + try { + const post = await createPost(); + const input = await c.req.json(); + + return c.json( + { + status: 'success', + message: `Post created in subreddit ${context.subredditName} with id ${post.id} (trigger: ${input.type})`, + }, + 200 + ); + } catch (error) { + console.error(`Error creating post: ${error}`); + return c.json( + { + status: 'error', + message: 'Failed to create post', + }, + 400 + ); + } +}); diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json deleted file mode 100644 index 2d5c487..0000000 --- a/src/server/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -// TypeScript config for all Devvit server code. -{ - "extends": "../../tools/tsconfig-base.json", - "compilerOptions": { - "lib": ["ES2023"], - - "types": ["node"], - - "rootDir": ".", - - "outDir": "../../dist/types/server", - - "tsBuildInfoFile": "../../dist/types/server/tsconfig.tsbuildinfo" - }, - // https://github.com/Microsoft/TypeScript/issues/25636 - "include": ["**/*", "**/*.json", "../../package.json"], - "exclude": ["**/*.test.ts"], - "references": [{ "path": "../shared" }] -} diff --git a/src/server/vite.config.ts b/src/server/vite.config.ts deleted file mode 100644 index afff048..0000000 --- a/src/server/vite.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from "vite"; -import { builtinModules } from "node:module"; - -export default defineConfig({ - ssr: { - noExternal: true, - }, - logLevel: 'warn', - build: { - ssr: "index.ts", - outDir: "../../dist/server", - emptyOutDir: true, - target: "node22", - sourcemap: true, - rollupOptions: { - external: [...builtinModules], - output: { - format: "cjs", - entryFileNames: "index.cjs", - inlineDynamicImports: true, - }, - }, - }, -}); diff --git a/src/shared/types/api.ts b/src/shared/api.ts similarity index 100% rename from src/shared/types/api.ts rename to src/shared/api.ts diff --git a/src/shared/tsconfig.json b/src/shared/tsconfig.json deleted file mode 100644 index e401ebc..0000000 --- a/src/shared/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -// TypeScript config for non-devvit and shared code. -{ - "extends": "../../tools/tsconfig-base.json", - "compilerOptions": { - "lib": ["WebWorker", "ES2023"], - - "outDir": "../../dist/types/shared", - - "tsBuildInfoFile": "../../dist/types/shared/tsconfig.tsbuildinfo" - }, - // https://github.com/Microsoft/TypeScript/issues/25636 - "include": ["**/*", "**/*.json"], - "exclude": ["**/*.test.ts"] -} From e87d7c94826e85b4f679e6706f627fe439ebb258 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 14:38:07 -0500 Subject: [PATCH 2/8] more tweaks --- .gitignore | 7 ------ devvit.json | 4 +++ src/client/splash.html | 15 +++++++---- ...{tsconfig-base.json => tsconfig.base.json} | 25 +++---------------- tools/tsconfig.client.json | 16 ++++++++++++ tools/tsconfig.server.json | 18 +++++++++++++ tools/tsconfig.shared.json | 14 +++++++++++ tools/tsconfig.vite.json | 11 ++++++++ tsconfig.json | 8 +++--- vite.config.ts | 6 +++++ 10 files changed, 87 insertions(+), 37 deletions(-) rename tools/{tsconfig-base.json => tsconfig.base.json} (55%) create mode 100644 tools/tsconfig.client.json create mode 100644 tools/tsconfig.server.json create mode 100644 tools/tsconfig.shared.json create mode 100644 tools/tsconfig.vite.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index ced998e..c3b1f22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ node_modules -.yarn -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* .DS_Store -webroot dist .env \ No newline at end of file diff --git a/devvit.json b/devvit.json index ecaa87e..5a24c7c 100644 --- a/devvit.json +++ b/devvit.json @@ -30,5 +30,9 @@ }, "triggers": { "onAppInstall": "/internal/on-app-install" + }, + "scripts": { + "build": "vite build", + "dev": "vite build --watch" } } diff --git a/src/client/splash.html b/src/client/splash.html index de9fbc7..65bd733 100644 --- a/src/client/splash.html +++ b/src/client/splash.html @@ -1,7 +1,7 @@ - + - + devvit - Snoo + Snoo

- Edit src/client/splash/splash.ts to modify this screen. + Edit src/client/splash/splash.ts to modify + this screen.

@@ -28,6 +33,6 @@

| - + diff --git a/tools/tsconfig-base.json b/tools/tsconfig.base.json similarity index 55% rename from tools/tsconfig-base.json rename to tools/tsconfig.base.json index 27fd2bd..738af4e 100644 --- a/tools/tsconfig-base.json +++ b/tools/tsconfig.base.json @@ -1,12 +1,7 @@ -// TypeScript config defaults for each sub-project (src, test, etc). { "$schema": "https://json.schemastore.org/tsconfig.json", - "compilerOptions": { - // Enable incremental builds. "composite": true, - - // Maximize type checking. "allowUnreachableCode": false, "allowUnusedLabels": false, "exactOptionalPropertyTypes": true, @@ -18,27 +13,15 @@ "noUnusedParameters": true, "resolveJsonModule": true, "strict": true, - - "types": [], // Projects add types needed. - - // Improve compatibility with compilers that aren't type system aware. + "types": [], "isolatedModules": true, - - // Use ESM. "esModuleInterop": true, - - // Allow JSON type-checking and imports. "module": "ESNext", "moduleResolution": "Bundler", - - // Assume library types are checked and compatible. "skipLibCheck": true, "skipDefaultLibCheck": true, - "sourceMap": true, - - "target": "ES2022" - }, - // https://github.com/microsoft/TypeScript/wiki/Performance#misconfigured-include-and-exclude - "exclude": ["dist", "node_modules", ".*/"] + "target": "ES2022", + "lib": ["DOM", "ES2023", "esnext.disposable"] + } } diff --git a/tools/tsconfig.client.json b/tools/tsconfig.client.json new file mode 100644 index 0000000..9d03b1d --- /dev/null +++ b/tools/tsconfig.client.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist/types/client", + "tsBuildInfoFile": "../dist/types/client/tsconfig.tsbuildinfo", + "customConditions": ["browser"], + "rootDir": "../src/client" + }, + "include": ["../src/client/**/*"], + "exclude": [], + "references": [ + { + "path": "./tsconfig.shared.json" + } + ] +} diff --git a/tools/tsconfig.server.json b/tools/tsconfig.server.json new file mode 100644 index 0000000..eb03bbd --- /dev/null +++ b/tools/tsconfig.server.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist/types/server", + "tsBuildInfoFile": "../dist/types/server/tsconfig.tsbuildinfo", + "customConditions": [], + "types": ["node"], + "exactOptionalPropertyTypes": false, + "rootDir": "../src/server" + }, + "include": ["../src/server/**/*"], + "exclude": [], + "references": [ + { + "path": "./tsconfig.shared.json" + } + ] +} diff --git a/tools/tsconfig.shared.json b/tools/tsconfig.shared.json new file mode 100644 index 0000000..cf5d6cd --- /dev/null +++ b/tools/tsconfig.shared.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "lib": ["WebWorker", "ES2023"], + + "outDir": "../dist/types/shared", + + "tsBuildInfoFile": "../dist/shared/tsconfig.tsbuildinfo", + + "rootDir": "../src/shared" + }, + "include": ["../src/shared/**/*"], + "exclude": [] +} diff --git a/tools/tsconfig.vite.json b/tools/tsconfig.vite.json new file mode 100644 index 0000000..0bb0c78 --- /dev/null +++ b/tools/tsconfig.vite.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "outDir": "../dist/types/tools", + "tsBuildInfoFile": "../dist/types/tools/tsconfig.vite.tsbuildinfo", + "types": ["node"], + "rootDir": ".." + }, + "include": ["../vite.config.ts"], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json index ad713b8..f1edc59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { - // Only build references. "files": [], "references": [ - { "path": "./src/client" }, - { "path": "./src/shared" }, - { "path": "./src/server" } + { "path": "./tools/tsconfig.client.json" }, + { "path": "./tools/tsconfig.server.json" }, + { "path": "./tools/tsconfig.shared.json" }, + { "path": "./tools/tsconfig.vite.json" } ] } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..c4fd976 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import { devvit } from '@devvit/start/vite'; + +export default defineConfig({ + plugins: [devvit()], +}); From e6fabde1cc9513d7ca33bf50391ce21eae14d109 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 15:06:31 -0500 Subject: [PATCH 3/8] fix endpoint and prettier --- devvit.json | 2 +- src/client/splash.ts | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/devvit.json b/devvit.json index 5a24c7c..8a32125 100644 --- a/devvit.json +++ b/devvit.json @@ -29,7 +29,7 @@ ] }, "triggers": { - "onAppInstall": "/internal/on-app-install" + "onAppInstall": "/internal/triggers/on-app-install" }, "scripts": { "build": "vite build", diff --git a/src/client/splash.ts b/src/client/splash.ts index edc9703..6c3ea22 100644 --- a/src/client/splash.ts +++ b/src/client/splash.ts @@ -1,32 +1,32 @@ -import { navigateTo, context, requestExpandedMode } from "@devvit/web/client"; +import { navigateTo, context, requestExpandedMode } from '@devvit/web/client'; -const docsLink = document.getElementById("docs-link") as HTMLDivElement; -const playtestLink = document.getElementById("playtest-link") as HTMLDivElement; -const discordLink = document.getElementById("discord-link") as HTMLDivElement; +const docsLink = document.getElementById('docs-link') as HTMLDivElement; +const playtestLink = document.getElementById('playtest-link') as HTMLDivElement; +const discordLink = document.getElementById('discord-link') as HTMLDivElement; const startButton = document.getElementById( - "start-button" + 'start-button' ) as HTMLButtonElement; -startButton.addEventListener("click", (e) => { - requestExpandedMode(e, "game"); +startButton.addEventListener('click', (e) => { + requestExpandedMode(e, 'game'); }); -docsLink.addEventListener("click", () => { - navigateTo("https://developers.reddit.com/docs"); +docsLink.addEventListener('click', () => { + navigateTo('https://developers.reddit.com/docs'); }); -playtestLink.addEventListener("click", () => { - navigateTo("https://www.reddit.com/r/Devvit"); +playtestLink.addEventListener('click', () => { + navigateTo('https://www.reddit.com/r/Devvit'); }); -discordLink.addEventListener("click", () => { - navigateTo("https://discord.com/invite/R7yu2wh9Qz"); +discordLink.addEventListener('click', () => { + navigateTo('https://discord.com/invite/R7yu2wh9Qz'); }); -const titleElement = document.getElementById("title") as HTMLHeadingElement; +const titleElement = document.getElementById('title') as HTMLHeadingElement; function init() { - titleElement.textContent = `Hey ${context.username ?? "user"} 👋`; + titleElement.textContent = `Hey ${context.username ?? 'user'} 👋`; } init(); From d02b164db8944434d55f8ef4e419f1d001dd37d9 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 15:15:25 -0500 Subject: [PATCH 4/8] fix pacakge.json name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa02617..8d34f4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "name": "foo1231-33", + "name": "<% name %>", "version": "0.0.0", "license": "BSD-3-Clause", "type": "module", From 8989e1e4211c93a225eac7c0b183e07192236dec Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 15:33:18 -0500 Subject: [PATCH 5/8] rm dep --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 8d34f4a..37797b4 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "prettier": "3.8.1", "typescript": "5.9.3", "typescript-eslint": "8.54.0", - "vite": "7.3.1", - "vitest": "4.0.18" + "vite": "7.3.1" } } From b8e2b0355979862572708afb3db553a0d5cf9eb7 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Mon, 2 Feb 2026 15:48:25 -0500 Subject: [PATCH 6/8] form --- README.md | 2 +- devvit.json | 10 ++++++++++ src/server/routes/menu.ts | 21 --------------------- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7d57e19..0d41332 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A starter to build web applications on Reddit's developer platform - [Devvit](https://developers.reddit.com/): A way to build and deploy immersive games on Reddit -- [Express](https://expressjs.com/): For backend logic +- [Hono](https://hono.dev/): For backend logic - [TypeScript](https://www.typescriptlang.org/): For type safety - [Unity](https://www.unity.com/): For gameplay diff --git a/devvit.json b/devvit.json index 8a32125..027a0c9 100644 --- a/devvit.json +++ b/devvit.json @@ -25,9 +25,19 @@ "location": "subreddit", "forUserType": "moderator", "endpoint": "/internal/menu/post-create" + }, + { + "label": "Example form", + "description": "Show a simple form", + "location": "subreddit", + "forUserType": "moderator", + "endpoint": "/internal/menu/example-form" } ] }, + "forms": { + "exampleForm": "/internal/form/example-submit" + }, "triggers": { "onAppInstall": "/internal/triggers/on-app-install" }, diff --git a/src/server/routes/menu.ts b/src/server/routes/menu.ts index e2482d0..9a6def8 100644 --- a/src/server/routes/menu.ts +++ b/src/server/routes/menu.ts @@ -25,24 +25,3 @@ menu.post('/post-create', async (c) => { ); } }); - -menu.post('/example-form', async (c) => { - return c.json( - { - showForm: { - name: 'exampleForm', - form: { - title: 'Example Form', - fields: [ - { - type: 'string', - name: 'message', - label: 'Message', - }, - ], - }, - }, - }, - 200 - ); -}); From f09cdf197417e807b55044bd08a0f99c72309961 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Wed, 4 Feb 2026 15:47:33 -0500 Subject: [PATCH 7/8] agents --- AGENTS.md | 52 ++++++++++++++++++++++++++++++++++++++ tools/tsconfig.shared.json | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f1e18b7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,52 @@ +You are writing a Devvit web application that will be executed on Reddit.com. + +## Tech Stack + +- **Frontend**: Unity, Vite +- **Backend**: Node.js v22 serverless environment (Devvit), Hono, TRPC +- **Communication**: tRPC v11 for end-to-end type safety + +## Layout & Architecture + +- `/src/server`: **Backend Code**. This runs in a secure, serverless environment. + - `trpc.ts`: Defines the API router and procedures. + - `index.ts`: Main server entry point (Hono app). + - Access `redis`, `reddit`, and `context` here via `@devvit/web/server`. +- `/src/client`: **Frontend Code**. This is executed inside of an iFrame on reddit.com + - To add an entrypoint, create a HTML file and add to the mapping inside of `devvit.json` + - Entrypoints: + - `game.html`: The main React entry point (Expanded View). + - `splash.html`: The initial React entry point (Inline View). This will be shown in the reddit.com feed. Please keep it fast and keep heavy dependencies inside of `game.html` +- `/src/shared`: **Shared Code**. Code to share between the client and server + +## Frontend + +### Rules + +- Instead of `window.location` or `window.assign`, use `navigateTo` from `@devvit/web/client` + +### Limitations + +- `window.alert`: Use `showToast` or `showForm` from `@devvit/web/client` +- File downloads: Use clipboard API with `showToast` to confirm +- Geolocation, camera, microphone, and notifications web APIs: No alternatives +- Inline script tags inside of `html` files: Use a script tag and separate js/ts file + +## Commands + +- `npm run type-check`: Check typescript types +- `npm run lint`: Check the linter +- `npm run test -- my-file-name`: Run tests isolated to a file + +## Code Style + +- Prefer type aliases over interfaces when writing typescript +- Prefer named exports over default exports +- Never cast typescript types + +## Global Rules + +- You may find code that references blocks or `@devvit/public-api` while building a feature. Do NOT use this code as this project is configured to use Devvit web only. +- Whenever you add an endpoint for a new menu item action, ensure that you've added the corresponding mapping to `devvit.json` so that it is properly registered + +Docs: https://developers.reddit.com/docs/llms.txt. diff --git a/tools/tsconfig.shared.json b/tools/tsconfig.shared.json index cf5d6cd..a9e11ec 100644 --- a/tools/tsconfig.shared.json +++ b/tools/tsconfig.shared.json @@ -5,7 +5,7 @@ "outDir": "../dist/types/shared", - "tsBuildInfoFile": "../dist/shared/tsconfig.tsbuildinfo", + "tsBuildInfoFile": "../dist/types/shared/tsconfig.tsbuildinfo", "rootDir": "../src/shared" }, From c171e91dd861251106fc8c5ad90d4650335c6ad4 Mon Sep 17 00:00:00 2001 From: Marcus Wood Date: Wed, 4 Feb 2026 15:52:12 -0500 Subject: [PATCH 8/8] move public --- .../Build/SampleGame.data.unityweb | Bin .../public => public}/Build/SampleGame.framework.js | 0 .../public => public}/Build/SampleGame.loader.js | 0 .../Build/SampleGame.wasm.unityweb | Bin {src/client/public => public}/Snoo_Yes.png | Bin .../public => public}/TemplateData/favicon.ico | Bin .../TemplateData/progress-bar-empty-dark.png | Bin .../TemplateData/progress-bar-full-dark.png | Bin .../client/public => public}/TemplateData/style.css | 0 .../TemplateData/unity-logo-dark.png | Bin .../TemplateData/unity-logo-light.png | Bin .../TemplateData/unity-logo-title-footer.png | Bin 12 files changed, 0 insertions(+), 0 deletions(-) rename {src/client/public => public}/Build/SampleGame.data.unityweb (100%) rename {src/client/public => public}/Build/SampleGame.framework.js (100%) rename {src/client/public => public}/Build/SampleGame.loader.js (100%) rename {src/client/public => public}/Build/SampleGame.wasm.unityweb (100%) rename {src/client/public => public}/Snoo_Yes.png (100%) rename {src/client/public => public}/TemplateData/favicon.ico (100%) rename {src/client/public => public}/TemplateData/progress-bar-empty-dark.png (100%) rename {src/client/public => public}/TemplateData/progress-bar-full-dark.png (100%) rename {src/client/public => public}/TemplateData/style.css (100%) rename {src/client/public => public}/TemplateData/unity-logo-dark.png (100%) rename {src/client/public => public}/TemplateData/unity-logo-light.png (100%) rename {src/client/public => public}/TemplateData/unity-logo-title-footer.png (100%) diff --git a/src/client/public/Build/SampleGame.data.unityweb b/public/Build/SampleGame.data.unityweb similarity index 100% rename from src/client/public/Build/SampleGame.data.unityweb rename to public/Build/SampleGame.data.unityweb diff --git a/src/client/public/Build/SampleGame.framework.js b/public/Build/SampleGame.framework.js similarity index 100% rename from src/client/public/Build/SampleGame.framework.js rename to public/Build/SampleGame.framework.js diff --git a/src/client/public/Build/SampleGame.loader.js b/public/Build/SampleGame.loader.js similarity index 100% rename from src/client/public/Build/SampleGame.loader.js rename to public/Build/SampleGame.loader.js diff --git a/src/client/public/Build/SampleGame.wasm.unityweb b/public/Build/SampleGame.wasm.unityweb similarity index 100% rename from src/client/public/Build/SampleGame.wasm.unityweb rename to public/Build/SampleGame.wasm.unityweb diff --git a/src/client/public/Snoo_Yes.png b/public/Snoo_Yes.png similarity index 100% rename from src/client/public/Snoo_Yes.png rename to public/Snoo_Yes.png diff --git a/src/client/public/TemplateData/favicon.ico b/public/TemplateData/favicon.ico similarity index 100% rename from src/client/public/TemplateData/favicon.ico rename to public/TemplateData/favicon.ico diff --git a/src/client/public/TemplateData/progress-bar-empty-dark.png b/public/TemplateData/progress-bar-empty-dark.png similarity index 100% rename from src/client/public/TemplateData/progress-bar-empty-dark.png rename to public/TemplateData/progress-bar-empty-dark.png diff --git a/src/client/public/TemplateData/progress-bar-full-dark.png b/public/TemplateData/progress-bar-full-dark.png similarity index 100% rename from src/client/public/TemplateData/progress-bar-full-dark.png rename to public/TemplateData/progress-bar-full-dark.png diff --git a/src/client/public/TemplateData/style.css b/public/TemplateData/style.css similarity index 100% rename from src/client/public/TemplateData/style.css rename to public/TemplateData/style.css diff --git a/src/client/public/TemplateData/unity-logo-dark.png b/public/TemplateData/unity-logo-dark.png similarity index 100% rename from src/client/public/TemplateData/unity-logo-dark.png rename to public/TemplateData/unity-logo-dark.png diff --git a/src/client/public/TemplateData/unity-logo-light.png b/public/TemplateData/unity-logo-light.png similarity index 100% rename from src/client/public/TemplateData/unity-logo-light.png rename to public/TemplateData/unity-logo-light.png diff --git a/src/client/public/TemplateData/unity-logo-title-footer.png b/public/TemplateData/unity-logo-title-footer.png similarity index 100% rename from src/client/public/TemplateData/unity-logo-title-footer.png rename to public/TemplateData/unity-logo-title-footer.png