• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
jaballadares

jaballadares

sharesheet_thing

Remix of c15r/ProtoShare
Public
Like
sharesheet_thing
Home
Code
9
backend
5
frontend
3
shared
1
.vtignore
AGENTS.md
CLAUDE.md
README.md
deno.json
test-phase2.html
Branches
1
Pull requests
Remixes
History
Environment variables
4
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.
Sign up now
Code
/
/
x
/
jaballadares
/
sharesheet_thing
/
code
/
CLAUDE.md
/
CLAUDE.md
Code
/
/
x
/
jaballadares
/
sharesheet_thing
/
code
/
CLAUDE.md
/
CLAUDE.md
Search
…
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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/sqlite for persistence
  • React 18.2.0 with TailwindCSS for the frontend
  • WebAuthn for passwordless authentication
  • Bearer tokens for iOS shortcut API access

Important Platform Constraints

Val Town Specifics

  • DO NOT use Node.js APIs - this is Deno runtime
  • DO NOT use Deno keyword in shared/ directory - shared code must work in both frontend and backend
  • Use https://esm.sh for 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.fetch as the entry point for HTTP vals
  • Use new Response(null, { status: 302, headers: { Location: "/path" } }) for redirects (NOT Response.redirect)
  • Environment variables accessed via Deno.env.get('VAR_NAME')

Database Constraints

  • 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

Project Structure

β”œβ”€β”€ 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

Key Patterns

Static File Serving

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>`);

Authentication Flow

WebAuthn for User Sessions:

  1. POST /api/auth/register-start - Generate challenge for registration
  2. POST /api/auth/register-finish - Verify credential, create user, set session cookie
  3. POST /api/auth/login-start - Generate challenge for login
  4. POST /api/auth/login-finish - Verify credential, set session cookie
  5. Session stored in httpOnly cookie named session

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/content with Authorization: Bearer <token> header
  • Token validation in requireBearerToken middleware

Initial Data Injection Pattern

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'); } }, []);

Page Routing

The app uses server-side page type injection rather than client-side routing:

  • Backend sets pageType in window.__INITIAL_DATA__ based on route
  • Frontend App.tsx switches on pageType to render components
  • Navigation uses window.location.href for full page reloads

Error Handling

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 });

Hono Middleware Pattern

// 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 // ... });

Common Development Commands

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.

Environment Variables

Set in Val Town environment:

  • ADMIN_EMAIL - Email address that receives admin privileges on registration

Critical Gotchas

  1. SQLite Schema Changes: Can't ALTER TABLE easily. Create new table with suffix (_2, _3) and migrate data.

  2. WebAuthn Implementation: The current implementation is simplified for demo purposes. In production, use a proper WebAuthn library for signature verification.

  3. Session Management: Currently stores user ID directly in cookie (simplified). Production should use signed JWT or session tokens.

  4. Shared Code Restrictions: The shared/ directory code runs in BOTH frontend and backend. No Deno APIs, no server-only imports.

  5. Import Maps: React uses import map in index.html - ensure all React imports reference 18.2.0.

  6. CORS Not Needed: Backend and frontend are served from same origin on Val Town.

API Endpoints Reference

Authentication

  • POST /api/auth/register-start - Start registration
  • POST /api/auth/register-finish - Complete registration
  • POST /api/auth/login-start - Start login
  • POST /api/auth/login-finish - Complete login
  • POST /api/auth/logout - Clear session
  • GET /api/auth/me - Get current user
  • GET /api/auth/tokens - List bearer tokens
  • POST /api/auth/tokens - Create bearer token
  • DELETE /api/auth/tokens/:id - Delete token

Content

  • POST /api/content - Add content (Bearer token auth)
  • GET /api/content - List user's content (Session auth)
  • DELETE /api/content/:id - Delete content
  • GET /api/content/health - Health check for shortcuts
  • GET /api/content/debug - Debug database state

Admin (requires admin role)

  • GET /api/admin/users - List all users
  • GET /api/admin/content - List all content
  • POST /api/admin/users/:id/admin - Grant admin privileges
  • DELETE /api/admin/content/:id - Delete any content

Pages

  • GET / - Main app
  • GET /login - Login page
  • GET /register - Registration page
  • GET /dashboard - User dashboard
  • GET /admin - Admin panel
  • GET /setup - iOS shortcut setup guide
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
Β© 2025 Val Town, Inc.