Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when the user wants to build and put online a full-stack web app - a site or tool with a real backend: database, API endpoints, user accounts, file uploads - and no hosting or backend is set up yet. Gipity provides the whole stack (hosting, Postgres, serverless functions, auth) from the CLI, ending with a live URL.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 420% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 327% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 279% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 301% | 0% |
<!-- GENERATED from platform/docs/skills/web-app-basics.md by platform/scripts/sync-claude-plugin.ts - do not edit here. -->
> Gipity required. This skill needs the gipity CLI linked to a project. If gipity status errors or shows no project, run the setup flow in the gipity skill first (in Claude Code or Grok: /gipity:setup; in Codex or any other agent, follow the gipity skill's setup steps directly). > > This doc is shared across Gipity surfaces; where it names an agent tool, use the CLI equivalent: add → gipity add <name>, file_write/file_read/file_delete → edit files in the project directory directly (they auto-sync), project_deploy → gipity deploy dev, code_execute → gipity sandbox run. The live version of this doc: gipity skill read web-app-basics.
When building apps or websites, follow these practices for professional-quality output.
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 files wired up and ready to build on - no demo to delete first. 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.
Where things live (web-simple) - what to edit: For a content or markup change, edit src/index.html. For visible display text (labels, button copy), edit src/js/strings.js. For styling, edit src/css/styles.css. src/js/main.js holds the app logic. The rest - config.js, i18n.js, settings.js, translations.js - is boilerplate you only open when enabling i18n or feature flags. Don't read every file before a simple edit; go straight to the one that owns the thing you're changing.
Templates install real files - Read one before you change it. add writes a full set of starter files (HTML/CSS/JS, gipity.yaml, functions, and more), already on disk with placeholders ({{TITLE}}, …) substituted - so they are not new files. A blind file_write on one you haven't read fails with "File has not been read yet", and editing from memory of the template misses the exact-string match (the title is already baked into <h1>, not {{TITLE}}) and loops. One file_read of the file you're about to change defuses both - just that file, not the whole tree.
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.
src/ - src/index.html, src/css/styles.css, src/js/main.js, src/images/index.html, styles.css, and app.js (or main.js). Never inline large blocks of CSS or JS in HTML.src/css/, src/js/, src/assets/, src/sounds/, src/images/, etc.sounds/click.ogg, images/logo.png). Never copy files to the root just for convenience - deployed apps serve the full directory tree.index.html clean - it should be structure/markup, not behavior or styling<header>, <nav>, <main>, <section>, <footer>, <article><meta name="viewport" content="width=device-width, initial-scale=1.0"><title> and favicon link<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">:root for custom colors/fontsconst/let, arrow functions, template literals, and modern ES6+ syntaxDOMContentLoaded or place script at end of bodyaddEventListener - never inline onclick attributes in HTML<script type="importmap"> block in <head>: <script type="importmap"> { "imports": { "lodash-es": "https://esm.sh/lodash-es@4.17.21" } } </script> Then in JS: import { debounce } from 'lodash-es';
cdn.jsdelivr.net/npm/) - Use when the package ships a browser-ready ES module file (Three.js, Phaser, Rapier).?bundle-deps if it has dependencies.<script> tags (non-module) also work for libraries that expect a global (e.g. Phaser).utils.js, api.js, ui.js).temp, data2, stuff.js.config.js or constants.js), not scattered across the codebase.utils.js or helpers.js file. Small, pure functions are easy to test and reuse.*.test.js for unit tests, *.e2e.test.js for end-to-end tests. convert input.png -resize 1200x1200\> -quality 80 output.webp This keeps images under ~100KB for most web use. Use <img src="images/photo.webp"> in HTML.
src/ directory exists, only src/ is deployed. Otherwise the full project root is deployed.<script> tags MUST use type="module" (e.g. <script type="module" src="./js/main.js"></script>) - this is what the templates ship. Prod deploys run Vite optimization by default, which only traces module scripts; it aborts (it won't silently drop your JS) when it hits a plain same-origin <script src>. If you have an app that genuinely can't use modules, deploy it with gipity deploy prod --no-optimize to upload files as-is. CDN <script> tags pointing at external URLs (Phaser, etc.) are always fine without type="module".host_file to make workspace files publicly accessible via URL (max 50MB). Useful for images in emails or sharing files outside the app.Every template installs a full share/SEO head: <title>, meta description, canonical URL, the complete Open Graph + Twitter card set (including an absolute og:image), theme-color, favicon/apple-touch-icon/manifest links, and an application/ld+json structured-data object. The matching image assets (favicons, iOS Home Screen icon, PWA manifest, and a 1200x630 src/images/og-image.png share card) are generated at install from the app's title and description - so a link shared on X/iMessage/Slack shows a real card, and "Add to Home Screen" gets a real icon, with zero extra work.
Two rules keep that polish intact:
description when installing a template. It feeds the meta description, the og/twitter descriptions, and the share card's tagline. Without one, those tags are omitted and the shared link looks bare.<title>, the og:title/twitter:title, and the JSON-LD name together so link previews and search results match what the page actually shows. Updating only the <title> leaves a stale structured-data/social name behind. After a title/description change, refresh the generated images too: gipity brand apply.Custom app icon + share card: gipity brand set re-renders every generated asset deterministically - e.g. gipity brand set --emoji 🦍 (an emoji icon reads far more "this is my app" than the default letter), --color "#3b82f6" for the accent, --tagline "..." for the share-card subtitle. Then gipity deploy dev to publish. For fully custom art, overwrite src/images/og-image.png (1200x630) or the icon files directly (e.g. via gipity generate image) - brand apply regenerates them, so skip it after hand-replacing.
Older apps (installed before templates shipped this head): their index.html has no og:image/twitter/apple-touch-icon/manifest tags, so regenerated assets alone won't show up in link previews. Run gipity brand apply --fix-head once: it regenerates the assets AND splices the current share/SEO head block into src/index.html, preserving the page's own title and description. Then deploy.
A deployed app should not depend on a third-party CDN being up to perform its core function. The app's own files deploy to the Gipity CDN; a runtime import from esm.sh/unpkg/jsdelivr does not - if that CDN is slow, down, or the user is offline, the import fails and a feature that relies on it silently does nothing.
src/js/vendor/ and import it locally (e.g. import QRCode from './vendor/qrcode.js'). It deploys with the app to the Gipity CDN, so it loads as reliably as the rest of your code - no third-party runtime dependency, no import map pointing at an external host.esm.sh with ?bundle - it inlines every transitive dependency into a single file, so there's nothing left to re-fetch or rewrite. E.g. curl -sSL "https://esm.sh/chart.js@4?bundle" -o src/js/vendor/chart.js, then import { Chart } from './vendor/chart.js'. Don't use the UMD build (a plain <script> tag gets dropped by the prod build) or a jsdelivr /+esm URL (it leaves transitive deps as dangling external imports you'd have to vendor and rewrite by hand). esm.sh ... ?bundle avoids that whole rabbit hole.esm.sh answers ?bundle with a tiny (~100-byte) shim whose whole body is export * from "https://esm.sh/..." - a file that looks vendored but still hits the CDN at runtime, silently reintroducing exactly the dependency you were removing. After every vendor fetch, check both: ls -l (a real library is tens-to-hundreds of KB; a 3-digit byte count is a stub) and head -c 300 (any export ... from "https:// line means stub). If you got a stub, fetch the concrete build file the stub points at (its URL is right there in the stub body) and vendor that; then confirm the final file contains no from "https:// imports at all (grep -c 'from "https://' vendor/lib.js → 0).try/catch and show the user a clear message ("Couldn't load the QR generator - check your connection and retry") instead of leaving the UI doing nothing. A silent failure looks like a broken app.If the user asks for a QR code to the app/URL itself (not an in-app generator) - e.g. "put a QR code on the front desk" - actually produce the image. Generate it in the sandbox with qrencode (see the worked example in sandbox-tools), save the PNG into src/images/ so it deploys, optionally embed it on the page, and tell the user the file path. Don't hand back the URL and tell them to make the QR themselves - that leaves the explicit ask unfulfilled.
Deploy, then look at the page - never assume it worked. gipity deploy dev --inspect deploys and reports the live page in one step (console errors, failed resources, timing, layout overflow); gipity page screenshot <url> shows what it actually renders; gipity page test <url> --action <js> --observe <js> drives an interactive feature and asserts the headline behavior really works (e.g. "type a message → get an AI reply") instead of just proving the page loaded. Don't hand-roll a DOM-poking page eval script for that.
Full debugging loop → the app-debugging skill: every flag on inspect/screenshot/eval, reading function logs, calling a function directly, and what the headless browser can't test.
For non-trivial apps, don't write the whole thing in one pass. Work in small verified steps:
add tool / gipity add <template>) and deploy - confirm the starter renders.A 300+ line single-file rewrite is hard to debug - a single bad API call or typo can break everything silently. Small increments keep the failure surface tiny and let you bisect by diff.
When the request implies user-private data - "my receipts", a personal vault, private notes, journals, anything storing a user's own uploads or records - default to scoping storage and listing per authenticated user via app-auth. A "my X" app where anyone with the URL sees and can delete everyone else's data is a privacy hole, not just a missing feature. So: gate writes behind sign-in and key every row to ctx.auth.userGuid (the stable external id - not the internal numeric userId; see "Using auth" in app-development), and filter listings to the signed-in user. If you intentionally ship a public or shared version instead (e.g. a community wall), that's a valid choice - but say so explicitly in your summary so the user can decide, rather than shipping public-by-default silently. Load app-auth for the sign-in flow.
For a single-purpose utility (a QR generator, a color picker, a unit converter), doing only the bare ask ships something that works but feels unfinished. These tools have obvious adjacent affordances that are cheap to add and clearly raise quality. Keep it scoped: pick a couple of the cheap-polish moves below, not a feature dump.
web-ui-patterns rather than the generic AI-purple default.Don't bloat it - a couple of these turn a 4/5 into a 5/5; ten of them turn a simple tool into a confusing one.
For the concrete recipes behind this section - the default Gipity theme, entry lists/feeds, copy-to-clipboard - load web-ui-patterns.
Verify a deploy when it matters - the first deploy, structural changes (new pages, new frameworks, changed imports), or anything that might have broken. Skip it for trivial changes (copy tweaks, style values).
gipity deploy dev --inspect deploys and reports the live page in one step: console errors, failed resources, timing, layout overflow. A clean console is necessary but NOT sufficient for Canvas/WebGL - also capture gipity page screenshot <url> and look at it, because render failures are silent. A blank page, black canvas, or wrong-looking UI with a clean console is a real failure, not a pass.
Full loop - reading function logs, calling a function directly, driving the page: the app-debugging skill.
Building a game? Don't hand-roll it - add the template and load its skill. 3D or multiplayer (obby, tycoon, PvP, shooter): 3d-engine for a blank slate, 3d-world for a playable starter - Three.js + Rapier physics + Gipity Realtime, genre recipes in the skill. 2D (platformer, scroller, arcade, puzzle, endless runner): 2d-game - Phaser 3, no build step. Simple games with no engine need (wordle, quiz, cards) stay on web-simple.
Keyboard controls must ignore keys typed into form fields. A game's global keydown/keyup listeners (WASD/arrows/Space) also fire while the player types into an <input> - a high-score name field, a chat box - and any preventDefault() there makes those letters impossible to type (the classic symptom: "I can't type W/A/S/D into the name field"). Start every global key handler with:
jsif (e.target.closest('input, textarea, select') || e.target.isContentEditable) return;
and clear any held-key state when a field gains focus (focusin), so the player doesn't keep moving on a key whose release the game never saw. The game templates ship this guard; hand-rolled games on web-simple/web-fullstack must add it themselves. Test name-entry UI by typing the movement keys into it.
A few small rules turn a flaky click test into a reliable one:
data-testid (or a documented id) - buttons, inputs, list items, dialogs. Tests use those, never CSS selectors that leak layout.<body data-screen="home">, updated by your screen-switcher. A test then waits with waitForSelector('body[data-screen="lobby"]') instead of probing internal class / hidden state.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.
Load the ones that match what you're building - frontend recipes, game and 3D templates, i18n, or backend services:
web-ui-patterns - default Gipity look (theme tokens) + copy-paste web UI recipes (feeds, copy-to-clipboard)2d-game - 2D games with Phaser (platformer, scroller, arcade, puzzle, endless runner)3d-engine - minimal 3D multiplayer template (Three.js + Rapier + Gipity Realtime, no gameplay)3d-world - playable 3D multiplayer starter built on 3d-engine (obby, tycoon, simulator, PvP, shooter, etc.)app-development - Functions, database & APIapp-debugging - Debug a deployed app: page inspect/eval/screenshot, function logsapp-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 (Gipity Image)app-video - Video generation and understanding (Gipity Video)app-tts - Text-to-speech (Gipity Speech - multi-speaker, 60+ languages)app-audio - Sound effects, music generation, and audio transcriptionapp-files - File uploads (Gipity Storage, up to 30GB, progress tracking, thumbnails)Other measured skills in the registry, with their headline benchmark lift.