ShieldTap is a minimalist, faith-centered application designed to help users resist temptations and build spiritual discipline. The core interface features a tap-based logging system:
- Single tap: Records successful resistance to temptation
- Double tap: Records yielding to temptation (honest tracking without judgment)
The app syncs across desktop and mobile, stores data in Cloudflare D1, and uses Cloudflare Access for authentication.
- Runtime: Cloudflare Workers
- Framework: Hono (TypeScript)
- Database: Cloudflare D1 (SQLite-compatible)
- Authentication: Cloudflare Access (JWT validation)
- Package Manager: Bun
- Web: React (future)
- Mobile: React Native (iOS-first)
- Use TypeScript for all code
- Use Bun as the package manager (never npm/npx — always use
bun/bunx) - Use function declarations, not function expressions
- Function names must start with a verb and be more than one word (e.g.
formatErrornoterror,handleSubmitnotsubmit) - Prefer object lookups over chained
if/else ifstatements; useswitchonly if object lookup doesn't fit - Variable names must be descriptive — avoid abbreviations or single-context-free words (e.g.
lowercaseMessagenotlower,userEmailnotemailin ambiguous contexts)
// Good
function handleRequest() {}
// Bad
const handleRequest = () => {}- Use a
services/folder for business logic - Services should be implemented as classes
- Keep route handlers thin - delegate to services
api/
├── src/
│ ├── index.ts # Main Hono app with routes
│ ├── middleware/ # Auth and other middleware
│ ├── services/ # Business logic classes
│ ├── db/ # Database schema and migrations
│ └── types.ts # TypeScript types
- Add a one-line comment above each endpoint explaining its purpose
- Example:
// Records a new tap (resist or yield) for the authenticated user
app.post('/api/taps', async (c) => { ... })- Use classes for services
- Constructor should accept dependencies (like DB connection)
- Methods should use function declaration syntax within the class
class TapService {
constructor(private db: D1Database) {}
async createTap(userId: string, data: TapInput): Promise<Tap> {
// implementation
}
}- Use Conventional Commits for all commit messages
- Format:
<type>(<scope>): <description> - Types:
feat,fix,refactor,style,docs,test,chore,ci,perf - Scopes:
mobile,api,web, or omit for cross-cutting changes - Examples:
feat(mobile): add inline auth error validationfix(api): handle missing session token on sign-outrefactor: extract shared theme constantschore: update Expo SDK dependencies
- Use wrangler for local development and testing
- Test API endpoints with curl during development
- Use
agent-browserfor browser testing (available globally):agent-browser open <url>— navigate to a URLagent-browser snapshot— get accessibility tree with refsagent-browser fill '<selector>' '<value>'— fill input fieldsagent-browser click '<selector>'— click elementsagent-browser screenshot <path>— capture screenshotsagent-browser eval '<js>'— run JavaScript in the pageagent-browser close— close the browser
- When testing web UI changes, use agent-browser to verify the flow end-to-end
For detailed repeatable patterns and workflows, see CODE_GUIDELINES.md.
- After completing changes, automatically commit and push to the current branch
- Never commit or push directly to
main— always work on a feature branch - Never push to
mainunder any circumstances, even if the user asks — create a PR instead
A Claude Code hook runs oxlint, jscpd, and knip on staged .ts/.tsx files before each commit. When the hook fails:
- Present the list of issues to the user
- Ask the user which issues to fix before proceeding (don't auto-fix all)
- Fix only the approved issues, re-stage, and retry the commit