···11+# Val Town Development Guide
22+33+You are an advanced assistant that helps programmers code on Val Town.
44+55+## Core Guidelines
66+77+- Ask clarifying questions when requirements are ambiguous
88+- Plan large refactors or big features before you start coding
99+- Provide complete, functional solutions rather than skeleton implementations
1010+- Test your logic against edge cases before presenting the final solution
1111+- Ensure all code follows Val Town's specific platform requirements
1212+- Always prefer small, single purpose, single responsibility components over
1313+ large files that do many things
1414+- If a section of code is getting too complex, consider refactoring it
1515+ into subcomponents
1616+- **Frontend = React, Backend = Hono, Database = Drizzle** - This is the way
1717+- **Write testable code** - Use dependency injection, follow SOLID principles,
1818+ mock external services, write fakes instead of testing dependencies
1919+2020+## Code Standards
2121+2222+- Generate code in TypeScript or TSX
2323+- Add appropriate TypeScript types and interfaces for all data structures
2424+- Prefer official SDKs or libraries than writing API calls directly
2525+- Ask the user to supply API or library documentation if you are at all unsure
2626+ about it
2727+- **Never bake in secrets into the code** - always use environment variables
2828+- Include comments explaining complex logic (avoid commenting obvious
2929+ operations)
3030+- Follow modern ES6+ conventions and functional programming practices if
3131+ possible
3232+- **Every function must be testable** - Accept dependencies as parameters,
3333+ return predictable outputs
3434+- **Write tests alongside code** - Place `.test.ts` files next to the code they
3535+ test
3636+3737+## Project Structure
3838+3939+### Project Organization
4040+4141+When organizing a Val Town project, consider separating deployable code from
4242+local resources:
4343+4444+```text
4545+├── your-project-name/ # Deployable directory (what Val Town will see)
4646+│ ├── backend/
4747+│ │ ├── database/
4848+│ │ │ ├── schema.ts # All table definitions, relations, and types
4949+│ │ │ ├── db.ts # Database connection and Drizzle instance
5050+│ │ │ ├── migrations.ts # Schema migration logic
5151+│ │ │ └── queries.ts # Reusable query functions
5252+│ │ ├── routes/ # Route modules
5353+│ │ │ ├── [route].ts
5454+│ │ │ └── static.ts # Static file serving
5555+│ │ └── index.ts # Main entry point
5656+│ ├── frontend/
5757+│ │ ├── components/
5858+│ │ │ ├── App.tsx
5959+│ │ │ └── [Component].tsx
6060+│ │ ├── index.html # Minimal HTML bootstrap file
6161+│ │ ├── index.tsx # React entry point with createRoot
6262+│ │ └── style.css # Global styles (prefer Tailwind classes)
6363+│ ├── shared/
6464+│ │ └── utils.ts # Shared types and functions
6565+│ └── deno.json # Deno configuration (MUST be in the deployed directory)
6666+├── resources/ # Local-only resources (images, assets)
6767+├── docs/ # Local-only documentation
6868+├── README.md # Project documentation
6969+└── AGENTS.md # AI assistant instructions
7070+```
7171+7272+**Key Points:**
7373+7474+- Only the contents of your main project directory will be deployed to Val Town
7575+- The `deno.json` file MUST be inside the deployment directory
7676+- Keep non-deployable resources outside the deployment directory
7777+7878+### Deno Configuration
7979+8080+Place your `deno.json` in the deployable directory:
8181+8282+```json
8383+{
8484+ "tasks": {
8585+ "quality": "deno fmt && deno lint --fix && deno check **/*.ts **/*.tsx && deno test",
8686+ "deploy": "deno task quality && vt push",
8787+ "check": "deno check **/*.ts **/*.tsx",
8888+ "test": "deno test",
8989+ "fmt": "deno fmt",
9090+ "lint": "deno lint --fix"
9191+ }
9292+}
9393+```
9494+9595+## Frontend Architecture
9696+9797+**Always use React** for Val Town frontends. Here's the opinionated approach:
9898+9999+### Frontend Core Principles
100100+101101+- **HTML is just a bootstrap file** - Keep it minimal, only load React
102102+- **No HTML fallbacks** - JavaScript is required, period
103103+- **Single Page Application** - Let React handle all rendering
104104+- **TypeScript everywhere** - Use `.tsx` files for all components
105105+- **Tailwind for styling** - Use the CDN version:
106106+ `<script src="https://cdn.twind.style" crossorigin></script>`
107107+108108+### React Configuration
109109+110110+- **Use latest versions** - Import React without version constraints
111111+- **JSX pragma required** - Start every `.tsx` file with
112112+ `/** @jsxImportSource https://esm.sh/react */`
113113+- **No build step** - Import directly from ESM URLs
114114+- **Client-side only** - No SSR, inject initial data if needed
115115+116116+## Backend Architecture
117117+118118+### Hono Framework
119119+120120+- Main entry point should be `backend/index.ts`
121121+- Export with `export default app.fetch`
122122+- Do NOT use Hono's serveStatic middleware
123123+- Use Val Town's `serveFile` utility for static assets
124124+- Re-throw errors in error handler for full stack traces
125125+126126+### API Design
127127+128128+- Create RESTful routes for CRUD operations
129129+- Use TypeScript interfaces for request/response types
130130+- Bootstrap initial data by reading and modifying HTML
131131+- Let errors bubble up with full context
132132+133133+## Database with Drizzle ORM
134134+135135+Val Town uses Turso (SQLite) under the hood. Use Drizzle ORM for type safety.
136136+137137+### Key Concepts
138138+139139+- **Schema-first approach** - Define all tables in `schema.ts`
140140+- **Single database instance** - Create one connection in `db.ts`
141141+- **Type-safe queries** - Use Drizzle's query builder
142142+- **Migrations** - Track schema changes with versioned migrations
143143+144144+### Database Best Practices
145145+146146+- Use singular table names (e.g., `user` not `users`)
147147+- Define relations for efficient querying
148148+- Add indexes** on foreign keys and frequently queried columns
149149+- Use transactions** for multi-table operations
150150+- Handle SQLite limitations - No complex ALTER TABLE
151151+- Limited ALTER TABLE support
152152+- Change table names instead of altering
153153+- Always run migrations before queries
154154+155155+### Common Patterns
156156+157157+- **Simple CRUD**: Use Drizzle's select, insert, update, delete
158158+- **Relations**: Use `db.query` for nested data
159159+- **Complex joins**: Use select with join methods
160160+- **Raw SQL**: Use `db.run(sql`)`` when needed
161161+162162+## Testing Strategy
163163+164164+### Testing Core Principles
165165+166166+- Write tests first or ensure code is testable
167167+- Place test files next to code: `user.ts` → `user.test.ts`
168168+- Mock all external dependencies
169169+- Test behavior, not implementation
170170+171171+### Writing Testable Code
172172+173173+```typescript
174174+// BAD: Hard to test
175175+export async function getUser(id: string) {
176176+ const user = await db.select().from(userTable).where(eq(userTable.id, id));
177177+ return user[0];
178178+}
179179+180180+// GOOD: Testable with dependency injection
181181+export async function getUser(id: string, dbProvider = db) {
182182+ const user = await dbProvider.select().from(userTable).where(
183183+ eq(userTable.id, id),
184184+ );
185185+ return user[0];
186186+}
187187+```
188188+189189+### SOLID Principles
190190+191191+1. **Single Responsibility** - Each function does one thing
192192+2. **Open/Closed** - Use composition over modification
193193+3. **Liskov Substitution** - Interfaces should be substitutable
194194+4. **Interface Segregation** - Small, focused interfaces
195195+5. **Dependency Inversion** - Depend on abstractions
196196+197197+### Running Tests
198198+199199+```bash
200200+deno test # Run all tests
201201+deno test user.test.ts # Run specific test
202202+deno task quality # Includes tests in quality check
203203+```
204204+205205+## TypeScript Configuration
206206+207207+### Type Checking
208208+209209+- Use `deno check` before deployment
210210+- Add explicit types for function parameters and returns
211211+- Define interfaces for all data structures
212212+- External libraries from esm.sh include types automatically
213213+214214+### TypesSript Best Practices
215215+216216+- Leverage TypeScript's strict mode
217217+- Type API responses for client-side safety
218218+- Use type-only imports: `import type { SomeType }`
219219+- Ignore Bun-specific errors if they appear
220220+221221+## Dependency Management
222222+223223+### Import Strategy
224224+225225+- **Use latest versions** - No version pinning needed
226226+- **Import from esm.sh**: `https://esm.sh/package`
227227+- **Trust CDN resolution** - esm.sh handles compatibility
228228+- **Central deps file** - Use `deps.ts` for complex projects
229229+230230+### Common Imports
231231+232232+```typescript
233233+// React (with pragma)
234234+/** @jsxImportSource https://esm.sh/react */
235235+import React from "https://esm.sh/react";
236236+237237+// Drizzle ORM
238238+import { drizzle } from "https://esm.sh/drizzle-orm/libsql";
239239+240240+// Hono
241241+import { Hono } from "https://esm.sh/hono";
242242+```
243243+244244+## Development Workflow
245245+246246+### Task Commands
247247+248248+1. **During development**: `deno task check` - Catch type errors
249249+2. **Before committing**: `deno task quality` - Format, lint, type check, test
250250+3. **To deploy**: `deno task deploy` - Quality checks then deploy
251251+252252+### Deployment Process
253253+254254+```bash
255255+deno task deploy # Runs quality checks first
256256+```
257257+258258+- Fix any issues before deployment succeeds
259259+- Environment variables managed in Val Town interface
260260+- Only deployment directory contents are uploaded
261261+262262+## Val Town APIs
263263+264264+### Triggers
265265+266266+1. **HTTP Trigger** - Web APIs and endpoints
267267+2. **Cron Triggers** - Scheduled tasks (1 min minimum on pro)
268268+3. **Email Triggers** - Process incoming emails
269269+270270+### Standard Libraries
271271+272272+- **Blob Storage**: `import { blob } from "https://esm.town/v/std/blob"`
273273+- **SQLite**: Use Drizzle ORM instead of raw SQL
274274+- **OpenAI**: `import { OpenAI } from "https://esm.town/v/std/openai"`
275275+- **Email**: `import { email } from "https://esm.town/v/std/email"`
276276+277277+### Utility Functions
278278+279279+### Importing Utilities
280280+281281+Always import utilities with version pins to avoid breaking changes:
282282+283283+```ts
284284+import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
285285+```
286286+287287+### Available Utilities
288288+289289+**serveFile** - Serve project files with proper content types
290290+291291+For example, in Hono:
292292+293293+```ts
294294+// serve all files in frontend/ and shared/
295295+app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
296296+app.get("/shared/*", c => serveFile(c.req.path, import.meta.url));
297297+```
298298+299299+**readFile** - Read files from within the project:
300300+301301+```ts
302302+// Read a file from the project
303303+const fileContent = await readFile("/frontend/index.html", import.meta.url);
304304+```
305305+306306+**listFiles** - List all files in the project
307307+308308+```ts
309309+const files = await listFiles(import.meta.url);
310310+```
311311+312312+## Platform Specifics
313313+314314+### Important Limitations
315315+316316+- **Redirects**: Use
317317+ `new Response(null, { status: 302, headers: { Location: "/path" }})`
318318+- **No binary files** - Text files only
319319+- **No Deno KV** - Use SQLite instead
320320+- **No browser APIs** - No alert(), prompt(), confirm()
321321+- **Automatic CORS** - Don't import CORS middleware
322322+323323+## Common Gotchas
324324+325325+### Environment
326326+327327+- Val Town runs on Deno, not Node.js
328328+- Shared code can't use Deno-specific APIs
329329+- Use esm.sh for browser/server compatibility
330330+331331+### Hono Issues
332332+333333+- NEVER import serveStatic middleware
334334+- NEVER import CORS middleware
335335+- Use Val Town's utilities instead
336336+- **ALWAYS import Val Town utilities directly** - Never create placeholder
337337+ functions for `serveFile`, `blob`, `email`, etc. Import them from
338338+ `https://esm.town/v/std/utils/index.ts` or their respective standard library modules
339339+ from the start
340340+341341+## Migration Strategy
342342+343343+### Principles
344344+345345+- Track migration history in dedicated table
346346+- Make migrations idempotent
347347+- Control with environment variables
348348+- Test thoroughly before deployment
349349+350350+### Best Practices
351351+352352+- Plan schema changes carefully
353353+- Consider performance impact
354354+- Backup before destructive changes
355355+- Remember SQLite limitations
356356+- Use emojis/unicode instead of images
357357+- Let errors bubble up with context
358358+- Prefer APIs without keys (e.g., open-meteo for weather)
359359+- Add error debugging script:
360360+ `<script src="https://esm.town/v/std/catch"></script>`
361361+- **Never create placeholder functions** for Val Town utilities - always import
362362+ the real ones directly, even during development
+129
README.md
···11+# Storygraph ↔ Goodreads CSV Converter
22+33+A simple web service hosted on Val Town that converts reading data between Storygraph and Goodreads CSV formats.
44+55+## Features
66+77+- **Bidirectional conversion**: Convert from Storygraph to Goodreads format and vice versa
88+- **Smart field mapping**: Automatically maps compatible fields between formats
99+- **Goodreads enrichment**: Automatically enriches book data with complete titles and author information from Goodreads API
1010+- **ISBN handling**: Parses and converts between ISBN-10 and ISBN-13 formats
1111+- **Date format conversion**: Handles different date formats between platforms
1212+- **Format mapping**: Converts book formats (digital ↔ Kindle Edition, etc.)
1313+- **Rate limiting**: Respectful API usage with built-in rate limiting for Goodreads requests
1414+- **Temporary storage**: Files are stored temporarily and auto-deleted after 24 hours
1515+- **Progress tracking**: Real-time upload and conversion progress with detailed status updates
1616+- **Clean UI**: Simple drag-and-drop interface with format selection
1717+1818+## How to Use
1919+2020+1. **Choose conversion direction**: Select whether you're converting from Storygraph to Goodreads or vice versa
2121+2. **Upload your CSV**: Drop your export file or click to browse and select it
2222+3. **Download converted file**: Once processing is complete, download your converted CSV file
2323+4. **Import to target platform**: Use the downloaded file to import your reading data
2424+2525+## Field Mapping
2626+2727+### Storygraph → Goodreads
2828+- **Direct mappings**: Title, Authors, Star Rating, Review, Read Status, Read Count, Date Added, Last Date Read
2929+- **Smart conversions**:
3030+ - Sequential Book IDs (1, 2, 3...)
3131+ - ISBN/UID → separate ISBN and ISBN13 fields
3232+ - Format → Binding (digital → Kindle Edition, etc.)
3333+ - Authors + Contributors → Author + Additional Authors
3434+- **Goodreads enrichment**: Automatically looks up books on Goodreads to get complete titles and normalized author names
3535+- **Empty fields**: Publisher, Number of Pages, Year Published, Average Rating (not available in Storygraph)
3636+3737+### Goodreads → Storygraph
3838+- **Direct mappings**: Title, Star Rating, Review, Read Status, Read Count, Date Added, Date Read
3939+- **Smart conversions**:
4040+ - Author + Additional Authors → Authors field
4141+ - ISBN13/ISBN → ISBN/UID field
4242+ - Binding → Format (Kindle Edition → digital, etc.)
4343+- **Empty fields**: Moods, Pace, Character Development fields, Content Warnings (not available in Goodreads)
4444+4545+## Technical Details
4646+4747+### Architecture
4848+- **Backend**: Hono framework with TypeScript
4949+- **Frontend**: React with TypeScript
5050+- **Storage**: Val Town blob storage (temporary, 24-hour cleanup)
5151+- **Deployment**: Val Town platform
5252+5353+### File Structure
5454+```
5555+storygraph-to-goodreads/
5656+├── backend/
5757+│ ├── index.ts # Main Hono server
5858+│ └── utils/
5959+│ ├── converter.ts # CSV conversion logic
6060+│ └── goodreads-enricher.ts # Goodreads API integration
6161+├── frontend/
6262+│ ├── index.html # Bootstrap HTML
6363+│ ├── index.tsx # React app entry
6464+│ ├── components/
6565+│ │ └── App.tsx # Main app component
6666+│ └── style.css # Styles
6767+├── shared/
6868+│ └── types.ts # Shared TypeScript types
6969+├── cleanup-cron.ts # Cron job for blob cleanup
7070+└── deno.json # Deno configuration
7171+```
7272+7373+### API Endpoints
7474+- `POST /api/convert` - Upload and convert CSV file
7575+- `GET /api/download/:key` - Download converted file
7676+- `GET /api/status/:key` - Check conversion status
7777+- `GET /api/health` - Health check
7878+7979+## Privacy & Data Handling
8080+8181+- **No permanent storage**: All files are temporarily stored and automatically deleted after 24 hours
8282+- **No tracking**: No user data is collected or tracked
8383+- **Minimal external calls**: Only makes API calls to Goodreads for book enrichment (optional feature)
8484+- **Rate limited**: Respectful API usage with built-in delays between requests
8585+- **Secure**: Files are stored with random keys and cleaned up automatically
8686+8787+## Development
8888+8989+### Prerequisites
9090+- Deno runtime
9191+- Val Town account
9292+9393+### Commands
9494+```bash
9595+deno task check # Type check
9696+deno task fmt # Format code
9797+deno task lint # Lint code
9898+deno task test # Run tests
9999+deno task quality # Run all quality checks + tests
100100+deno task deploy # Deploy to Val Town
101101+```
102102+103103+### Testing
104104+The conversion logic includes comprehensive tests for:
105105+- ISBN parsing and conversion
106106+- Date format handling
107107+- Field mapping accuracy
108108+- Bidirectional conversion integrity
109109+- Goodreads API integration and enrichment
110110+- Rate limiting functionality
111111+112112+## Limitations
113113+114114+- CSV files only (no other formats supported)
115115+- Some platform-specific fields cannot be converted (e.g., Goodreads ratings → Storygraph moods)
116116+- File size limited by Val Town blob storage limits
117117+- Temporary storage only (24-hour retention)
118118+119119+## Contributing
120120+121121+This project is built for Val Town deployment. To contribute:
122122+1. Fork the repository
123123+2. Make your changes
124124+3. Test with `deno task quality`
125125+4. Submit a pull request
126126+127127+## License
128128+129129+MIT License - feel free to use and modify as needed.
+290
storygraph-to-goodreads/.cursorrules
···11+You are an advanced assistant specialized in generating Val Town code.
22+33+## Core Guidelines
44+55+- Ask clarifying questions when requirements are ambiguous
66+- Provide complete, functional solutions rather than skeleton implementations
77+- Test your logic against edge cases before presenting the final solution
88+- Ensure all code follows Val Town's specific platform requirements
99+- If a section of code that you're working on is getting too complex, consider refactoring it into subcomponents
1010+1111+## Code Standards
1212+1313+- Generate code in TypeScript or TSX
1414+- Add appropriate TypeScript types and interfaces for all data structures
1515+- Prefer official SDKs or libraries than writing API calls directly
1616+- Ask the user to supply API or library documentation if you are at all unsure about it
1717+- **Never bake in secrets into the code** - always use environment variables
1818+- Include comments explaining complex logic (avoid commenting obvious operations)
1919+- Follow modern ES6+ conventions and functional programming practices if possible
2020+2121+## Types of triggers
2222+2323+### 1. HTTP Trigger
2424+2525+- Create web APIs and endpoints
2626+- Handle HTTP requests and responses
2727+- Example structure:
2828+2929+```ts
3030+export default async function (req: Request) {
3131+ return new Response("Hello World");
3232+}
3333+```
3434+3535+Files that are HTTP triggers have http in their name like `foobar.http.tsx`
3636+3737+### 2. Cron Triggers
3838+3939+- Run on a schedule
4040+- Use cron expressions for timing
4141+- Example structure:
4242+4343+```ts
4444+export default async function () {
4545+ // Scheduled task code
4646+}
4747+```
4848+4949+Files that are Cron triggers have cron in their name like `foobar.cron.tsx`
5050+5151+### 3. Email Triggers
5252+5353+- Process incoming emails
5454+- Handle email-based workflows
5555+- Example structure:
5656+5757+```ts
5858+export default async function (email: Email) {
5959+ // Process email
6060+}
6161+```
6262+6363+Files that are Email triggers have email in their name like `foobar.email.tsx`
6464+6565+6666+## Val Town Standard Libraries
6767+6868+Val Town provides several hosted services and utility functions.
6969+7070+### Blob Storage
7171+7272+```ts
7373+import { blob } from "https://esm.town/v/std/blob";
7474+await blob.setJSON("myKey", { hello: "world" });
7575+let blobDemo = await blob.getJSON("myKey");
7676+let appKeys = await blob.list("app_");
7777+await blob.delete("myKey");
7878+```
7979+8080+### SQLite
8181+8282+```ts
8383+import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
8484+const TABLE_NAME = 'todo_app_users_2';
8585+// Create table - do this before usage and change table name when modifying schema
8686+await sqlite.execute(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
8787+ id INTEGER PRIMARY KEY AUTOINCREMENT,
8888+ name TEXT NOT NULL
8989+)`);
9090+// Query data
9191+const result = await sqlite.execute(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [1]);
9292+```
9393+9494+Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
9595+9696+### OpenAI
9797+9898+```ts
9999+import { OpenAI } from "https://esm.town/v/std/openai";
100100+const openai = new OpenAI();
101101+const completion = await openai.chat.completions.create({
102102+ messages: [
103103+ { role: "user", content: "Say hello in a creative way" },
104104+ ],
105105+ model: "gpt-4o-mini",
106106+ max_tokens: 30,
107107+});
108108+```
109109+110110+### Email
111111+112112+```ts
113113+import { email } from "https://esm.town/v/std/email";
114114+// By default emails the owner of the val
115115+await email({
116116+ subject: "Hi",
117117+ text: "Hi",
118118+ html: "<h1>Hi</h1>"
119119+});
120120+```
121121+122122+## Val Town Utility Functions
123123+124124+Val Town provides several utility functions to help with common project tasks.
125125+126126+### Importing Utilities
127127+128128+Always import utilities with version pins to avoid breaking changes:
129129+130130+```ts
131131+import { parseProject, readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
132132+```
133133+134134+### Available Utilities
135135+136136+137137+#### **serveFile** - Serve project files with proper content types
138138+139139+For example, in Hono:
140140+141141+```ts
142142+// serve all files in frontend/ and shared/
143143+app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
144144+app.get("/shared/*", c => serveFile(c.req.path, import.meta.url));
145145+```
146146+147147+#### **readFile** - Read files from within the project:
148148+149149+```ts
150150+// Read a file from the project
151151+const fileContent = await readFile("/frontend/index.html", import.meta.url);
152152+```
153153+154154+#### **listFiles** - List all files in the project
155155+156156+```ts
157157+const files = await listFiles(import.meta.url);
158158+```
159159+160160+#### **parseProject** - Extract information about the current project from import.meta.url
161161+162162+This is useful for including info for linking back to a val, ie in "view source" urls:
163163+164164+```ts
165165+const projectVal = parseProject(import.meta.url);
166166+console.log(projectVal.username); // Owner of the project
167167+console.log(projectVal.name); // Project name
168168+console.log(projectVal.version); // Version number
169169+console.log(projectVal.branch); // Branch name
170170+console.log(projectVal.links.self.project); // URL to the project page
171171+```
172172+173173+However, it's *extremely importing* to note that `parseProject` and other Standard Library utilities ONLY RUN ON THE SERVER.
174174+If you need access to this data on the client, run it in the server and pass it to the client by splicing it into the HTML page
175175+or by making an API request for it.
176176+177177+## Val Town Platform Specifics
178178+179179+- **Redirects:** Use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken
180180+- **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
181181+- **AI Image:** To inline generate an AI image use: `<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />`
182182+- **Storage:** DO NOT use the Deno KV module for storage
183183+- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
184184+- **Weather Data:** Use open-meteo for weather data (doesn't require API keys) unless otherwise specified
185185+- **View Source:** Add a view source link by importing & using `import.meta.url.replace("ems.sh", "val.town)"` (or passing this data to the client) and include `target="_top"` attribute
186186+- **Error Debugging:** Add `<script src="https://esm.town/v/std/catch"></script>` to HTML to capture client-side errors
187187+- **Error Handling:** Only use try...catch when there's a clear local resolution; Avoid catches that merely log or return 500s. Let errors bubble up with full context
188188+- **Environment Variables:** Use `Deno.env.get('keyname')` when you need to, but generally prefer APIs that don't require keys
189189+- **Imports:** Use `https://esm.sh` for npm and Deno dependencies to ensure compatibility on server and browser
190190+- **Storage Strategy:** Only use backend storage if explicitly required; prefer simple static client-side sites
191191+- **React Configuration:** When using React libraries, pin versions with `?deps=react@18.2.0,react-dom@18.2.0` and start the file with `/** @jsxImportSource https://esm.sh/react@18.2.0 */`
192192+- Ensure all React dependencies and sub-dependencies are pinned to the same version
193193+- **Styling:** Default to using TailwindCSS via `<script src="https://cdn.twind.style" crossorigin></script>` unless otherwise specified
194194+195195+## Project Structure and Design Patterns
196196+197197+### Recommended Directory Structure
198198+```
199199+├── backend/
200200+│ ├── database/
201201+│ │ ├── migrations.ts # Schema definitions
202202+│ │ ├── queries.ts # DB query functions
203203+│ │ └── README.md
204204+│ └── routes/ # Route modules
205205+│ ├── [route].ts
206206+│ └── static.ts # Static file serving
207207+│ ├── index.ts # Main entry point
208208+│ └── README.md
209209+├── frontend/
210210+│ ├── components/
211211+│ │ ├── App.tsx
212212+│ │ └── [Component].tsx
213213+│ ├── favicon.svg
214214+│ ├── index.html # Main HTML template
215215+│ ├── index.tsx # Frontend JS entry point
216216+│ ├── README.md
217217+│ └── style.css
218218+├── README.md
219219+└── shared/
220220+ ├── README.md
221221+ └── utils.ts # Shared types and functions
222222+```
223223+224224+### Backend (Hono) Best Practices
225225+226226+- Hono is the recommended API framework
227227+- Main entry point should be `backend/index.ts`
228228+- **Static asset serving:** Use the utility functions to read and serve project files:
229229+ ```ts
230230+ import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
231231+232232+ // serve all files in frontend/ and shared/
233233+ app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
234234+ app.get("/shared/*", c => serveFile(c.req.path, import.meta.url));
235235+236236+ // For index.html, often you'll want to bootstrap with initial data
237237+ app.get("/", async c => {
238238+ let html = await readFile("/frontend/index.html", import.meta.url);
239239+240240+ // Inject data to avoid extra round-trips
241241+ const initialData = await fetchInitialData();
242242+ const dataScript = `<script>
243243+ window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
244244+ </script>`;
245245+246246+ html = html.replace("</head>", `${dataScript}</head>`);
247247+ return c.html(html);
248248+ });
249249+ ```
250250+- Create RESTful API routes for CRUD operations
251251+- Always include this snippet at the top-level Hono app to re-throwing errors to see full stack traces:
252252+ ```ts
253253+ // Unwrap Hono errors to see original error details
254254+ app.onError((err, c) => {
255255+ throw err;
256256+ });
257257+ ```
258258+259259+### Database Patterns
260260+- Run migrations on startup or comment out for performance
261261+- Change table names when modifying schemas rather than altering
262262+- Export clear query functions with proper TypeScript typing
263263+264264+## Common Gotchas and Solutions
265265+266266+1. **Environment Limitations:**
267267+ - Val Town runs on Deno in a serverless context, not Node.js
268268+ - Code in `shared/` must work in both frontend and backend environments
269269+ - Cannot use `Deno` keyword in shared code
270270+ - Use `https://esm.sh` for imports that work in both environments
271271+272272+2. **SQLite Peculiarities:**
273273+ - Limited support for ALTER TABLE operations
274274+ - Create new tables with updated schemas and copy data when needed
275275+ - Always run table creation before querying
276276+277277+3. **React Configuration:**
278278+ - All React dependencies must be pinned to 18.2.0
279279+ - Always include `@jsxImportSource https://esm.sh/react@18.2.0` at the top of React files
280280+ - Rendering issues often come from mismatched React versions
281281+282282+4. **File Handling:**
283283+ - Val Town only supports text files, not binary
284284+ - Use the provided utilities to read files across branches and forks
285285+ - For files in the project, use `readFile` helpers
286286+287287+5. **API Design:**
288288+ - `fetch` handler is the entry point for HTTP vals
289289+ - Run the Hono app with `export default app.fetch // This is the entry point for HTTP vals`
290290+