Public
Like
1
kipclip-appview
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data β all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v41View latest version
You are an advanced assistant that helps programmers code on Val Town.
- Ask clarifying questions when requirements are ambiguous
- Plan large refactors or big features before you start coding
- Provide complete, functional solutions rather than skeleton implementations
- Test your logic against edge cases before presenting the final solution
- Ensure all code follows Val Town's specific platform requirements
- Always prefer small, single purpose, single responsibility components over large files that do many things
- If a section of code is getting too complex, consider refactoring it into subcomponents
- Frontend = React, Backend = Hono, Database = Drizzle - This is the way
- Write testable code - Use dependency injection, follow SOLID principles, mock external services, write fakes instead of testing dependencies
- Generate code in TypeScript or TSX
- Add appropriate TypeScript types and interfaces for all data structures
- Prefer official SDKs or libraries than writing API calls directly
- Ask the user to supply API or library documentation if you are at all unsure about it
- Never bake in secrets into the code - always use environment variables
- Include comments explaining complex logic (avoid commenting obvious operations)
- Follow modern ES6+ conventions and functional programming practices if possible
- Every function must be testable - Accept dependencies as parameters, return predictable outputs
- Write tests alongside code - Place
.test.tsfiles next to the code they test
When organizing a Val Town project, consider separating deployable code from local resources:
βββ your-project-name/ # Deployable directory (what Val Town will see) β βββ backend/ β β βββ database/ β β β βββ schema.ts # All table definitions, relations, and types β β β βββ db.ts # Database connection and Drizzle instance β β β βββ migrations.ts # Schema migration logic β β β βββ queries.ts # Reusable query functions β β βββ routes/ # Route modules β β β βββ [route].ts β β β βββ static.ts # Static file serving β β βββ index.ts # Main entry point β βββ frontend/ β β βββ components/ β β β βββ App.tsx β β β βββ [Component].tsx β β βββ index.html # Minimal HTML bootstrap file β β βββ index.tsx # React entry point with createRoot β β βββ style.css # Global styles (prefer Tailwind classes) β βββ shared/ β β βββ utils.ts # Shared types and functions β βββ deno.json # Deno configuration (MUST be in the deployed directory) βββ resources/ # Local-only resources (images, assets) βββ docs/ # Local-only documentation βββ README.md # Project documentation βββ AGENTS.md # AI assistant instructions
Key Points:
- Only the contents of your main project directory will be deployed to Val Town
- The
deno.jsonfile MUST be inside the deployment directory - Keep non-deployable resources outside the deployment directory
Place your deno.json in the deployable directory:
{ "tasks": { "quality": "deno fmt && deno lint --fix && deno check **/*.ts **/*.tsx && deno test", "deploy": "deno task quality && vt push", "check": "deno check **/*.ts **/*.tsx", "test": "deno test", "fmt": "deno fmt", "lint": "deno lint --fix" } }
Always use React for Val Town frontends. Here's the opinionated approach:
- HTML is just a bootstrap file - Keep it minimal, only load React
- No HTML fallbacks - JavaScript is required, period
- Single Page Application - Let React handle all rendering
- TypeScript everywhere - Use
.tsxfiles for all components - Tailwind for styling - Use the CDN version:
<script src="https://cdn.twind.style" crossorigin></script>
- Use latest versions - Import React without version constraints
- JSX pragma required - Start every
.tsxfile with/** @jsxImportSource https://esm.sh/react */ - No build step - Import directly from ESM URLs
- Client-side only - No SSR, inject initial data if needed
- Main entry point should be
backend/index.ts - Export with
export default app.fetch - Do NOT use Hono's serveStatic middleware
- Use Val Town's
serveFileutility for static assets - Re-throw errors in error handler for full stack traces
- Create RESTful routes for CRUD operations
- Use TypeScript interfaces for request/response types
- Bootstrap initial data by reading and modifying HTML
- Let errors bubble up with full context
Val Town uses Turso (SQLite) under the hood. Use Drizzle ORM for type safety.
- Schema-first approach - Define all tables in
schema.ts - Single database instance - Create one connection in
db.ts - Type-safe queries - Use Drizzle's query builder
- Migrations - Track schema changes with versioned migrations
- Use singular table names (e.g.,
usernotusers) - Define relations for efficient querying
- Add indexes** on foreign keys and frequently queried columns
- Use transactions** for multi-table operations
- Handle SQLite limitations - No complex ALTER TABLE
- Limited ALTER TABLE support
- Change table names instead of altering
- Always run migrations before queries
- Simple CRUD: Use Drizzle's select, insert, update, delete
- Relations: Use
db.queryfor nested data - Complex joins: Use select with join methods
- Raw SQL: Use
db.run(sql)`` when needed
- Write tests first or ensure code is testable
- Place test files next to code:
user.tsβuser.test.ts - Mock all external dependencies
- Test behavior, not implementation
// BAD: Hard to test
export async function getUser(id: string) {
const user = await db.select().from(userTable).where(eq(userTable.id, id));
return user[0];
}
// GOOD: Testable with dependency injection
export async function getUser(id: string, dbProvider = db) {
const user = await dbProvider.select().from(userTable).where(
eq(userTable.id, id),
);
return user[0];
}
- Single Responsibility - Each function does one thing
- Open/Closed - Use composition over modification
- Liskov Substitution - Interfaces should be substitutable
- Interface Segregation - Small, focused interfaces
- Dependency Inversion - Depend on abstractions
deno test # Run all tests deno test user.test.ts # Run specific test deno task quality # Includes tests in quality check
- Use
deno checkbefore deployment - Add explicit types for function parameters and returns
- Define interfaces for all data structures
- External libraries from esm.sh include types automatically
- Leverage TypeScript's strict mode
- Type API responses for client-side safety
- Use type-only imports:
import type { SomeType } - Ignore Bun-specific errors if they appear
- Use latest versions - No version pinning needed
- Import from esm.sh:
https://esm.sh/package - Trust CDN resolution - esm.sh handles compatibility
- Central deps file - Use
deps.tsfor complex projects
// React (with pragma)
/** @jsxImportSource https://esm.sh/react */
import React from "https://esm.sh/react";
// Drizzle ORM
import { drizzle } from "https://esm.sh/drizzle-orm/libsql";
// Hono
import { Hono } from "https://esm.sh/hono";
- During development:
deno task check- Catch type errors - Before committing:
deno task quality- Format, lint, type check, test - To deploy:
deno task deploy- Quality checks then deploy
deno task deploy # Runs quality checks first
- Fix any issues before deployment succeeds
- Environment variables managed in Val Town interface
- Only deployment directory contents are uploaded
- HTTP Trigger - Web APIs and endpoints
- Cron Triggers - Scheduled tasks (1 min minimum on pro)
- Email Triggers - Process incoming emails
- Blob Storage:
import { blob } from "https://esm.town/v/std/blob" - SQLite: Use Drizzle ORM instead of raw SQL
- OpenAI:
import { OpenAI } from "https://esm.town/v/std/openai" - Email:
import { email } from "https://esm.town/v/std/email"
Always import utilities with version pins to avoid breaking changes:
serveFile - Serve project files with proper content types
For example, in Hono:
// serve all files in frontend/ and shared/
app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url));
app.get("/shared/*", (c) => serveFile(c.req.path, import.meta.url));
readFile - Read files from within the project:
// Read a file from the project
const fileContent = await readFile("/frontend/index.html", import.meta.url);
listFiles - List all files in the project
- Redirects: Use
new Response(null, { status: 302, headers: { Location: "/path" }}) - No binary files - Text files only
- No Deno KV - Use SQLite instead
- No browser APIs - No alert(), prompt(), confirm()
- Automatic CORS - Don't import CORS middleware
- Val Town runs on Deno, not Node.js
- Shared code can't use Deno-specific APIs
- Use esm.sh for browser/server compatibility
- NEVER import serveStatic middleware
- NEVER import CORS middleware
- Use Val Town's utilities instead
- ALWAYS import Val Town utilities directly - Never create placeholder
functions for
serveFile,blob,email, etc. Import them fromhttps://esm.town/v/std/utils/index.tsor their respective standard library modules from the start
- Track migration history in dedicated table
- Make migrations idempotent
- Control with environment variables
- Test thoroughly before deployment
- Plan schema changes carefully
- Consider performance impact
- Backup before destructive changes
- Remember SQLite limitations
- Use emojis/unicode instead of images
- Let errors bubble up with context
- Prefer APIs without keys (e.g., open-meteo for weather)
- Add error debugging script:
<script src="https://esm.town/v/std/catch"></script> - Never create placeholder functions for Val Town utilities - always import the real ones directly, even during development