When building apps or websites, follow these practices for professional-quality output.
Getting Started - Start Here
STRONGLY RECOMMENDED: Start every new web app by adding a template with the add tool. Pick the right one (web-simple for static frontend-only, web-fullstack for backend+DB, api for pure API). It creates the standard src/ structure with favicon, meta tags, and working starter files (including a demo you can customize). Deploy automatically uses src/ when it exists. Only hand-roll files if the user explicitly tells you to skip the template.
Naming: Use the user's name verbatim if they gave one. If you need to invent a name, blend "Gip" or "Gipity" into it (e.g. "Gipity Notes", "GipPic", "Gip Tac Toe") - be creative but don't force it if it genuinely doesn't fit.
Starting over in an existing project: If src/ (or functions/, migrations/ for fullstack/api) already exists and the user wants a clean rebuild, call file_delete on those directories first, then run add normally. Or pass force=true to add to overwrite in one step - destructive, so confirm with the user first. Non-template content (media, data, notes) is preserved either way.
Multi-language (web-simple): The template ships a dormant i18n system. Flip config.features.i18n to true in src/js/config.js to enable the language picker and translations.js lookup; the code in src/js/strings.js, src/js/i18n.js, and src/js/main.js is self-documenting - read those to see the render() + i18n:changed event pattern.
File Structure
- Use src/ convention: All app files live under
src/-src/index.html,src/css/styles.css,src/js/main.js,src/images/ - Separate files: Split into
index.html,styles.css, andapp.js(ormain.js). Never inline large blocks of CSS or JS in HTML. - If the app grows, organize into folders:
src/css/,src/js/,src/assets/,src/sounds/,src/images/, etc. - Use subfolders - don't flatten: Reference assets from their folders (e.g.
sounds/click.ogg,images/logo.png). Never copy files to the root just for convenience - deployed apps serve the full directory tree. - Keep
index.htmlclean - it should be structure/markup, not behavior or styling
HTML
- Use semantic elements:
<header>,<nav>,<main>,<section>,<footer>,<article> - Always include
<meta name="viewport" content="width=device-width, initial-scale=1.0"> - Add a proper
<title>and favicon link - Unless the user specifies a different CSS framework, include Water.css for automatic styling:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"> - Water.css styles semantic HTML automatically (buttons, tables, forms, nav, cards) - no classes needed. It supports dark/light themes automatically. Add custom CSS on top for app-specific tweaks.
CSS
- When using Water.css, it handles base styling, resets, and typography - don't duplicate what it provides
- Water.css exposes CSS variables for theming - override them in
:rootfor custom colors/fonts - Use CSS custom properties (variables) for app-specific colors, spacing, and fonts
- Add smooth transitions on interactive elements (buttons, links, hover states)
JavaScript
- Use
const/let, arrow functions, template literals, and modern ES6+ syntax - Wait for DOM: wrap in
DOMContentLoadedor place script at end of body - Keep functions small and focused
- Use
addEventListener- never inlineonclickattributes in HTML
External Packages
- No npm install. Gipity apps are static - there is no node_modules or build server.
- Use import maps to load npm packages from CDN. Add a
<script type="importmap">block in<head>:
Then in JS:<script type="importmap"> { "imports": { "lodash-es": "https://esm.sh/lodash-es@4.17.21" } } </script>import { debounce } from 'lodash-es'; - jsdelivr (
cdn.jsdelivr.net/npm/) - Use when the package ships a browser-ready ES module file (Three.js, Phaser, Rapier). - esm.sh - Use for any npm package, especially those without a browser build. Add
?bundle-depsif it has dependencies. - CDN
<script>tags (non-module) also work for libraries that expect a global (e.g. Phaser).
Code Quality
- Keep files under ~400 lines unless the content genuinely requires it (e.g. a long data table, template string, or config object). When logic grows beyond that, split into focused modules (e.g.
utils.js,api.js,ui.js). - Don't duplicate code. If the same logic appears twice, extract it into a shared function. Before writing a new helper, check if one already exists or could be extended.
- One responsibility per file. A file that handles both UI rendering and API calls should be split.
- Name things clearly. Functions, variables, and files should describe what they do - no
temp,data2,stuff.js. - Prefer simple, readable code over clever code that hides bugs. Flat over nested - use early returns, avoid deep nesting.
- Centralize configuration. App settings, API URLs, feature flags, and magic numbers should live in a dedicated config file (e.g.
config.jsorconstants.js), not scattered across the codebase. - Write utility functions for repeated operations (formatting, validation, API calls). Keep them in a
utils.jsorhelpers.jsfile. Small, pure functions are easy to test and reuse.
Testing
- Write tests for new functions - especially utility/helper functions. Cover the happy path and edge cases (empty input, null, boundary values).
- Don't mock unless absolutely required. Tests should exercise real code paths. Only mock external paid services (APIs that cost money per call).
- E2E tests should hit real infrastructure (real API, real DB) - just clean up test data when done.
- Test file naming:
*.test.jsfor unit tests,*.e2e.test.jsfor end-to-end tests.
Images
- Optimize images for web. Generated images (DALL-E, Flux) are often 1-5MB PNGs. Before adding them to a web page, use the sandbox to convert to WebP at a reasonable size:
This keeps images under ~100KB for most web use. Useconvert input.png -resize 1200x1200\> -quality 80 output.webp<img src="images/photo.webp">in HTML. - Keep originals if the user wants them - but reference the optimized version in HTML/CSS.
- Size guide: Hero images ~1200px wide, thumbnails ~400px, icons ~64-128px. Don't serve a 4000px image in a 600px container.
- Use WebP as the default format for photos and generated images. Use PNG only for images that need transparency with sharp edges (logos, icons). Use SVG for simple graphics and icons when possible.
Deployment
- src/ detection: If a
src/directory exists, onlysrc/is deployed. Otherwise the full project root is deployed. - Local
<script>tags MUST usetype="module"(e.g.<script type="module" src="./js/main.js"></script>). Prod deploys run Vite optimization which only traces module scripts; a plain<script src="js/x.js">gets silently dropped from the build and the deployed page 404s on that JS file. CDN<script>tags pointing at external URLs (Phaser, etc.) are fine withouttype="module"- the guard only enforces this for same-origin paths. - Auto-deploy: When deploy mode is "auto-dev" or "auto-prod", ANY file change (write, edit, copy, move, delete) triggers automatic deployment
- Rate limit: Per-plan deploy-rate cap (shared between manual and auto)
- Caching: CloudFront cache is automatically invalidated on production deploys. Dev deploys use short cache TTLs.
- File hosting: Use
host_fileto make workspace files publicly accessible via URL (max 50MB). Useful for images in emails or sharing files outside the app.
Browser Debugging
Two separate browser capabilities - pick the right one:
gipity page-inspect <url> (CLI) - one-shot inspection. Returns console errors, failed resources, timing, oversized images. No actions, no screenshots. Use this first after every deploy.
Options: --wait <ms> (default 3000), --json. If unsure: gipity page-inspect --help.
browser agent tool - interactive debugging with actions. Use when the CLI inspection surfaces something you need to dig into:
open→snapshot- read DOM/accessibility treeconsole- capturedconsole.error/console.warneval- run JS expressions (check variables, DOM state)screenshot- always use for Canvas/WebGL; a clean console isn't proof the page renderedclick,fill,type,select- form/nav flows
Flow: deploy → gipity page-inspect <url> → if anything's off, switch to the agent tool.
Build Incrementally
For non-trivial apps, don't write the whole thing in one pass. Work in small verified steps:
- Add a template (
addtool /gipity add <template>) and deploy - confirm the starter renders. - Add ONE feature or screen, deploy, verify.
- Repeat.
A 300+ line single-file rewrite is hard to debug when something breaks - a single bad API call or typo can break everything silently. Small increments keep the failure surface tiny and let you bisect by diff.
Verification After Deploy
After gipity deploy dev:
- ALWAYS check the console (
gipity page-inspect <url>or thebrowseragent toolopenaction). - On the first deploy of a new app, and any time visual output matters (Canvas, WebGL, complex layout), also capture a screenshot and look at it. "Clean console" is necessary but NOT sufficient for Canvas/WebGL - render failures are often silent and the console interceptor can miss sync errors that fired during initial script parse.
- If you see a blank page, black canvas, or wrong-looking UI with a clean console: treat that as a real failure and investigate (screenshot,
evalDOM state, re-read skill docs for gotchas) - don't declare success.
Deploy Verification
Use the browser tool to verify deploys when it matters - first deploy, structural changes (new pages, new frameworks, changed imports), or when something might have broken. Skip verification for trivial changes (copy tweaks, style adjustments, config values).
To verify: browser action=open url=<deployed-url> - waits for async modules, captures console errors automatically. Check output for [Console errors captured after page load]. Use browser action=screenshot to confirm visual correctness.
Debugging in production: Add console.error() calls to app code for diagnostics, redeploy, then use browser action=console to read the output. Remove debug logging when done.
3D World
3D World is the 3D multiplayer game template on Gipity. All 3D World games share the same visual style, physics engine (Rapier), and multiplayer backend (Colyseus). All files are fully editable.
Add a 3D World project with add name=3d-world (web agent) or gipity add 3d-world (CLI). This creates a playable 3D game with Three.js + Rapier physics + Colyseus multiplayer. Key files: config.js (metadata), settings.js (tunable values), strings.js (display text), objects.js (entity factories), game.js (orchestrator), plus engine files (core.js, world.js, physics.js, etc.).
Genres: obby/parkour, tycoon, simulator, PvP combat, shooter, tower defense, horror, racing, RPG, social.
Features: Opt-in gameplay modules enabled via config.features. Available: rocket-launcher (projectile weapon with physics explosions). Example: features: { 'rocket-launcher': true } in config.js. Features auto-initialize during boot.
Regular game requests ("make a wordle", "build a quiz") should use the standard web scaffold - they don't need the 3D template.
Load 3d-engine for a blank-slate template, or 3d-world for the full API, genre recipes, and playable starter.
2D Game
2D Game is the Phaser-based game scaffold on Gipity. It creates a fully editable project with the Phaser 3 game engine loaded via CDN - no build step, no locked files.
Add a 2D game with add name=2d-game (web agent) or gipity add 2d-game (CLI). This creates a playable 2D game with arcade physics, keyboard input, and a Boot/Game scene structure. All files are editable: config.js (Phaser setup), settings.js (tunable values), strings.js (display text), scenes/boot.js (preloader), scenes/game.js (main gameplay).
Genres: side-scroller, platformer, top-down, arcade, puzzle, endless runner, shooter, RPG.
Use 2d-game when the user wants a 2D game with physics, sprites, or scene management. Use web for simple games (wordle, quiz, card games) that don't need a game engine. Use 3d-world for 3D multiplayer games.
Load 2d-game for the full Phaser API and genre recipes.
Make it testable
A few small rules turn a flaky click test into a reliable one:
- Every interactive element gets a stable
data-testid(or a documentedid) - buttons, inputs, list items, dialogs. Tests use those, never CSS selectors that leak layout. - Expose the current screen as a body attribute:
<body data-screen="home">, updated by your screen-switcher. A test then waits withwaitForSelector('body[data-screen="lobby"]')instead of probing internal class / hidden state. - A single readiness signal: set
document.body.dataset.ready = 'true'once the app's main loop is up and ready for input. Tests wait on that, not on guessed timeouts.
These are about ten lines of code in total. For multiplayer apps, also read the URL-param test mode pattern in app-realtime - it turns a click-driven 2-client test into two passive page loads.
Related App Skills
When building apps that need backend features, load these skills:
2d-game- 2D games with Phaser (platformer, scroller, arcade, puzzle, endless runner)3d-engine- minimal 3D multiplayer template (Three.js + Rapier + Colyseus, no gameplay)3d-world- playable 3D multiplayer starter built on 3d-engine (obby, tycoon, simulator, PvP, shooter, etc.)app-development- Functions, database & APIapp-llm- AI/LLM service for your appapp-auth- User authentication (Sign in with Gipity)app-realtime- Real-time multiplayer rooms and WebSocketapp-image- Image generation (OpenAI, BFL/Flux, Gemini/Nano Banana)app-video- Video generation (Veo 3.1) and video understanding (Gemini)app-tts- Text-to-speech (ElevenLabs, OpenAI, Gemini - multi-speaker, 60+ languages)app-audio- Sound effects, music generation, and audio transcriptionapp-files- File uploads (presigned S3, up to 30GB, progress tracking, thumbnails)