This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Val Town project - an iOS Share Sheet Backend with WebAuthn authentication. It's deployed as a serverless HTTP val that provides a backend for iOS share sheet shortcuts.
Key Architecture Points:
- Val Town runs on Deno (not Node.js) in a serverless context
- Uses Hono web framework for API routing
- Uses SQLite via
stevekrouse/sqlitefor persistence - React 18.2.0 with TailwindCSS for the frontend
- WebAuthn for passwordless authentication
- Bearer tokens for iOS shortcut API access
- DO NOT use Node.js APIs - this is Deno runtime
- DO NOT use
Denokeyword inshared/directory - shared code must work in both frontend and backend - Use
https://esm.shfor all imports that work in both environments - Always pin React to 18.2.0 with:
/** @jsxImportSource https://esm.sh/react@18.2.0 */ - Use
export default app.fetchas the entry point for HTTP vals - Use
new Response(null, { status: 302, headers: { Location: "/path" } })for redirects (NOTResponse.redirect) - Environment variables accessed via
Deno.env.get('VAR_NAME')
- SQLite via
stevekrouse/sqlite(NOT the Deno KV module) - Limited ALTER TABLE support - when changing schema, create new tables (e.g.,
users_2,users_3) - Run migrations on startup in
backend/index.ts - Migration file:
backend/database/migrations.ts - Query functions:
backend/database/queries.ts
βββ backend/
β βββ index.ts # Main Hono app entry point (exports app.fetch)
β βββ database/
β β βββ migrations.ts # Table definitions and indexes
β β βββ queries.ts # All database operations (CRUD functions)
β βββ routes/
β βββ auth.ts # WebAuthn registration/login + bearer token management
β βββ content.ts # Content CRUD (POST /api/content for shortcuts)
β βββ admin.ts # Admin-only endpoints
β βββ static.ts # Serves frontend pages with initial data injection
βββ frontend/
β βββ index.html # Main HTML template (includes TailwindCSS via CDN)
β βββ index.tsx # React entry point
β βββ components/
β βββ App.tsx # Root component with page routing
β βββ Auth.tsx # WebAuthn login/register UI
β βββ Dashboard.tsx # User content display
β βββ Admin.tsx # Admin interface
β βββ Setup.tsx # iOS shortcut setup guide
βββ shared/
βββ types.ts # TypeScript types shared between frontend/backend
The project uses Val Town utility functions for serving files:
import { serveFile, readFile } from "https://esm.town/v/std/utils@85-main/index.ts";
// Serve all files in a directory
app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url));
// Read a file to inject data
let html = await readFile("/frontend/index.html", import.meta.url);
html = html.replace("</head>", `<script>window.__DATA__ = {}</script></head>`);
WebAuthn for User Sessions:
POST /api/auth/register-start- Generate challenge for registrationPOST /api/auth/register-finish- Verify credential, create user, set session cookiePOST /api/auth/login-start- Generate challenge for loginPOST /api/auth/login-finish- Verify credential, set session cookie- Session stored in
httpOnlycookie namedsession
Bearer Tokens for iOS Shortcuts:
- Shortcuts cannot use cookies - they use Bearer tokens
POST /api/auth/tokens- Create bearer token (requires auth)- Shortcuts call
POST /api/contentwithAuthorization: Bearer <token>header - Token validation in
requireBearerTokenmiddleware
To avoid extra round-trips, the backend injects initial data into HTML:
// backend/routes/static.ts
const initialData = {
user: { ...user, isAdmin: adminEmail && user?.email === adminEmail },
pageType,
timestamp: new Date().toISOString(),
};
const dataScript = `<script>window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};</script>`;
html = html.replace("</head>", `${dataScript}</head>`);
Accessed in frontend:
// frontend/components/App.tsx
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
if (window.__INITIAL_DATA__) {
setUser(window.__INITIAL_DATA__.user);
setPageType(window.__INITIAL_DATA__.pageType || 'app');
}
}, []);
The app uses server-side page type injection rather than client-side routing:
- Backend sets
pageTypeinwindow.__INITIAL_DATA__based on route - Frontend
App.tsxswitches onpageTypeto render components - Navigation uses
window.location.hreffor full page reloads
Critical pattern - Hono wraps errors by default. This must be at the top level:
// backend/index.ts
app.onError((err, c) => {
throw err; // Re-throw to see full stack traces
});
// Authentication middleware
async function requireAuth(c: any, next: any) {
const sessionToken = getCookie(c, 'session');
if (!sessionToken) {
return c.json({ error: 'Authentication required' }, 401);
}
const user = await getUserById(sessionToken);
if (!user) {
return c.json({ error: 'Invalid session' }, 401);
}
c.set('user', user); // Store user in context
await next();
}
// Usage
auth.get('/tokens', requireAuth, async (c) => {
const user = c.get('user'); // Retrieve from context
// ...
});
This project is developed with the Val Town CLI (vt):
# Run the development server (local testing) vt run # Deploy to Val Town vt deploy # View logs vt logs # Open in browser vt open
Note: The vt CLI handles deployment to Val Town's infrastructure. The project doesn't use traditional build tools.
Set in Val Town environment:
ADMIN_EMAIL- Email address that receives admin privileges on registration
-
SQLite Schema Changes: Can't ALTER TABLE easily. Create new table with suffix (
_2,_3) and migrate data. -
WebAuthn Implementation: The current implementation is simplified for demo purposes. In production, use a proper WebAuthn library for signature verification.
-
Session Management: Currently stores user ID directly in cookie (simplified). Production should use signed JWT or session tokens.
-
Shared Code Restrictions: The
shared/directory code runs in BOTH frontend and backend. NoDenoAPIs, no server-only imports. -
Import Maps: React uses import map in
index.html- ensure all React imports reference18.2.0. -
CORS Not Needed: Backend and frontend are served from same origin on Val Town.
POST /api/auth/register-start- Start registrationPOST /api/auth/register-finish- Complete registrationPOST /api/auth/login-start- Start loginPOST /api/auth/login-finish- Complete loginPOST /api/auth/logout- Clear sessionGET /api/auth/me- Get current userGET /api/auth/tokens- List bearer tokensPOST /api/auth/tokens- Create bearer tokenDELETE /api/auth/tokens/:id- Delete token
POST /api/content- Add content (Bearer token auth)GET /api/content- List user's content (Session auth)DELETE /api/content/:id- Delete contentGET /api/content/health- Health check for shortcutsGET /api/content/debug- Debug database state
GET /api/admin/users- List all usersGET /api/admin/content- List all contentPOST /api/admin/users/:id/admin- Grant admin privilegesDELETE /api/admin/content/:id- Delete any content
GET /- Main appGET /login- Login pageGET /register- Registration pageGET /dashboard- User dashboardGET /admin- Admin panelGET /setup- iOS shortcut setup guide