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:
stevekrouse/sqlite for persistenceDeno keyword in shared/ directory - shared code must work in both frontend and backendhttps://esm.sh for all imports that work in both environments/** @jsxImportSource https://esm.sh/react@18.2.0 */export default app.fetch as the entry point for HTTP valsnew Response(null, { status: 302, headers: { Location: "/path" } }) for redirects (NOT Response.redirect)Deno.env.get('VAR_NAME')stevekrouse/sqlite (NOT the Deno KV module)users_2, users_3)backend/index.tsbackend/database/migrations.tsbackend/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 cookiehttpOnly cookie named sessionBearer Tokens for iOS Shortcuts:
POST /api/auth/tokens - Create bearer token (requires auth)POST /api/content with Authorization: Bearer <token> headerrequireBearerToken middlewareTo 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:
pageType in window.__INITIAL_DATA__ based on routeApp.tsx switches on pageType to render componentswindow.location.href for full page reloadsCritical 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 registrationSQLite 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. No Deno APIs, no server-only imports.
Import Maps: React uses import map in index.html - ensure all React imports reference 18.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 tokenPOST /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 stateGET /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 contentGET / - Main appGET /login - Login pageGET /register - Registration pageGET /dashboard - User dashboardGET /admin - Admin panelGET /setup - iOS shortcut setup guide