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.
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 with proper structure:
{ "$schema": "https://raw.githubusercontent.com/denoland/deno/348900b8b79f4a434cab4c74b3bc8d4d2fa8ee74/cli/schemas/config-file.v1.json", "lock": false, "compilerOptions": { "noImplicitAny": false, "strict": false, "jsx": "react-jsx", "jsxImportSource": "https://esm.sh/react", "types": [ "https://www.val.town/types/valtown.d.ts" ], "lib": [ "dom", "dom.iterable", "dom.asynciterable", "deno.ns", "deno.unstable" ] }, "lint": { "rules": { "exclude": [ "no-explicit-any", "no-import-prefix" ] } }, "include": [ "backend/", "frontend/", "shared/" ], "node_modules_dir": false, "experimental": { "unstable-node-globals": true, "unstable-temporal": true, "unstable-worker-options": true, "unstable-sloppy-imports": true }, "tasks": { "quality": "deno fmt && deno lint", "deploy": "deno task quality && vt push", "check": "deno check --allow-import backend/index.ts", "test": "deno test --allow-all tests/", "fmt": "deno fmt", "lint": "deno lint" } }
Key Points:
- Use
"include"at root level to specify which directories contain TypeScript files - Don't use glob patterns in lint/check commands - they fail silently
- Type checking requires
--allow-importflag for remote dependencies - Keep quality checks minimal for deployment (fmt + lint)
- Separate
checktask for local type checking - Exclude
no-import-prefixsince Val.town uses URL imports exclusively
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 explicit React import needed - With
react-jsxtransform, only import hooks/specific APIs - No build step - Import directly from ESM URLs
- Client-side only - No SSR, inject initial data if needed
- Guard browser code - Wrap DOM access with
typeof document !== "undefined"to prevent server-side execution
- 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
Get started with a template: