Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Copilot Instructions for satset.io

> Repository-wide instructions for GitHub Copilot coding agent.

## About This Project

satset.io is a fast, privacy-focused URL shortener built with modern web technologies. It features an offline-first architecture, allowing users to generate short URLs locally and sync them to the backend when ready.

**Key Characteristics:**
- Zero network latency for URL generation
- No login or tracking
- Auto-expiring URLs (14 days)
- Built on Cloudflare's edge platform

## Tech Stack

See [AGENTS.md](../AGENTS.md) for detailed technology information.

**Runtime:** Bun (dev) + Cloudflare Workers (prod)
**Backend:** Elysia + TypeBox
**Frontend:** React 19 + TanStack Router + TanStack Query
**Database:** Cloudflare D1 (SQLite)
**Cache:** Cloudflare KV

## Development Commands

```bash
# Setup
bun install # Install all dependencies
cp wrangler.toml.example wrangler.toml # Configure Cloudflare (for backend dev)
bun run db:init # Initialize local D1 database

# Development
bun dev # Start dev server (frontend on :3000, backend on :8787)
bun run dev:frontend # Frontend only
bun run dev:backend # Backend only

# Code Quality
bun run lint # Check code style with Biome
bun run lint:fix # Auto-fix code style issues

# Build & Deploy
bun run build # Build for production
bun run deploy # Build + deploy to Cloudflare
```

## Project Guidelines

### What to Work On
- **Do:** Bug fixes, feature enhancements, performance improvements, accessibility fixes, documentation updates
- **Do:** Security improvements, test coverage, code refactoring
- **Avoid:** Changing core architecture without discussion
- **Avoid:** Adding new dependencies without justification

### File Organization
- **Frontend routes:** `src/client/routes/` - File-based routing via TanStack Router
- **Shared components:** `src/client/components/` - Reusable React components
- **API endpoints:** `src/api/worker.ts` - Elysia routes
- **Backend utilities:** `src/api/lib/` - Security, rate limiting, cache, QR generation

### Code Style
- **Linting:** Use Biome for code formatting and linting
- **Always run:** `bun run lint:fix` before committing
- **TypeScript:** Strict mode enabled, no `any` types
- **Comments:** Only when necessary to explain complex logic

### Testing Your Changes
1. Run `bun dev` to start the development server
2. Test in browser at `http://localhost:3000`
3. Verify backend functionality at `http://localhost:8787`
4. Run `bun run lint` to check code style
5. Test offline functionality (DevTools → Network → Offline)

### Routing Specifics (TanStack Router)
- Routes are defined in `src/client/routes/` using file-based routing
- **After adding/removing routes:** Run `bunx tsr generate` to update route tree
- Route components are defined **inline** in route files (not imported)
- Never manually edit `src/client/routeTree.gen.ts` - it's auto-generated

### Security Requirements
- **Always validate URLs:** Use existing validation in `src/api/lib/security.ts`
- **Rate limiting:** Already implemented via KV, don't bypass
- **SSRF protection:** Block localhost, private IPs, link-local addresses
- **HTTPS only:** All shortened URLs must use HTTPS
- **No secrets in code:** Use environment variables and Wrangler secrets

### Important Files Not to Modify
- `src/client/routeTree.gen.ts` - Auto-generated by TanStack Router
- `bun.lock` - Managed by Bun package manager
- `.git/` - Git internals

### Boundaries & Constraints
- **No tracking code:** This is a privacy-focused app
- **No authentication:** Keep the app completely anonymous
- **No database migrations:** Schema changes require careful planning
- **No breaking API changes:** Maintain backward compatibility
- **Minimize dependencies:** Only add packages when truly necessary

## Architecture Patterns

### Offline-First Pattern
The app generates short URLs **locally** for instant UX, then syncs to backend when user commits:
1. User pastes URL → Generate alias + QR locally (instant)
2. User commits (copy link/download QR) → Add to localStorage queue
3. Online: POST to `/api/urls` → Store in D1 + cache in KV
4. Offline: Queue locally → Retry with exponential backoff when online

**Key Files:**
- `src/client/lib/sync.ts` - Queue management, retry logic
- `src/client/hooks/useUrlShortener.ts` - URL generation, commit flow
- `src/client/hooks/useSyncStatus.ts` - Sync state subscription

### API Design
- Use TypeBox for request/response validation in Elysia
- Define schemas in `src/api/schemas.ts`
- Handle errors gracefully with proper HTTP status codes
- Rate limit all public endpoints

## Common Tasks

### Adding a New Route
1. Create file in `src/client/routes/` (e.g., `about.tsx`)
2. Define route component inline in the file
3. Run `bunx tsr generate` to update route tree
4. Test navigation in browser

### Adding a New API Endpoint
1. Add schema in `src/api/schemas.ts` using TypeBox
2. Add route in `src/api/worker.ts` using Elysia
3. Apply security checks and rate limiting
4. Update API client in `src/client/lib/api.ts` if needed

### Styling Changes
1. Use Tailwind CSS v4 classes (CSS-first config)
2. Follow existing dark mode patterns via `useTheme()` hook
3. Use `clsx` for conditional classes
4. Test in both light and dark modes

### Database Changes
1. Update `src/api/db/schema.sql`
2. Consider data migration strategy
3. Test with `bun run db:init` (local)
4. Document migration steps for production

## Examples

### Good Commit Messages
```
✨ Add custom alias input field
🐛 Fix QR code download on Safari
♻️ Refactor sync queue with exponential backoff
🔒 Add SSRF protection for URL validation
📝 Update API documentation
🎨 Improve dark mode contrast in buttons
```

### Code Style Example
```typescript
// ✅ Good: Clear, typed, following conventions
interface ShortUrl {
alias: string;
originalUrl: string;
expiresAt: number;
}

export function generateAlias(length = 6): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from(
{ length },
() => chars[Math.floor(Math.random() * chars.length)]
).join('');
}

// ❌ Bad: No types, unclear naming
export function gen(l) {
let r = '';
for (let i = 0; i < l; i++) {
r += 'abcdefghijklmnopqrstuvwxyz0123456789'[Math.floor(Math.random() * 36)];
}
return r;
}
```

## Getting Help

- **Architecture questions:** See [AGENTS.md](../AGENTS.md) for detailed architecture
- **Cloudflare-specific issues:** Check [Cloudflare Workers docs](https://developers.cloudflare.com/workers/)
- **Deployment questions:** See deployment section in [README.md](../README.md)

## Additional Resources

- [Elysia Documentation](https://elysiajs.com)
- [TanStack Router Documentation](https://tanstack.com/router)
- [Cloudflare D1 Documentation](https://developers.cloudflare.com/d1)
- [Bun Documentation](https://bun.sh/docs)