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 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.

File Structure

HTML

CSS

JavaScript

External Packages

Code Quality

Testing

Images

Deployment

Keep page metadata consistent

The template ships a baseline social/SEO block in <head>: the <title>, an og:title (often og:description/twitter:* too), and an application/ld+json structured-data object whose name is set once at install. These three are one unit. When you change the page's visible title or H1 from the install default, update all three 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.

For example, if you retitle a page to a short label but leave the JSON-LD untouched:

<title>914</title>
<meta property="og:title" content="914">
<script type="application/ld+json">{"@type":"WebSite","name":"my-project-install-slug"}</script>  <!-- stale -->

Bring the JSON-LD name (and any social tags) in line with the new title:

<title>914</title>
<meta property="og:title" content="914">
<script type="application/ld+json">{"@type":"WebSite","name":"914"}</script>

Third-party libraries

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.

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.

Browser Debugging

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.

Build Incrementally

For non-trivial apps, don't write the whole thing in one pass. Work in small verified steps:

  1. Add a template (add tool / gipity add <template>) and deploy - confirm the starter renders.
  2. Add ONE feature or screen, deploy, verify.
  3. Repeat.

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.

Personal data defaults to per-user scoping

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.

Go One Step Past the Literal Request

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.

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.

Deploy Verification

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.

Games

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.

Make it testable

A few small rules turn a flaky click test into a reliable one:

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 Skills

Load the ones that match what you're building - frontend recipes, game and 3D templates, i18n, or backend services: