Generated: 2026-01-29 21:01
Commit: 2c6e406
Branch: chore/license
Browser-based image manipulation SPA (React 19 + Bun) for cropping, format conversion, and GIF frame extraction. Deployed to Cloudflare Workers. No server—fully client-side using Canvas API and File System Access API.
./
├── src/
│ ├── components/ # 7 feature components + ui/ subdirectory
│ │ └── ui/ # 8 shadcn/ui base components (see ui/AGENTS.md)
│ ├── lib/ # Shared utilities (cn, settings, fileSystem, gallery)
│ ├── frontend.tsx # React entry point (actual entry, not index.ts)
│ ├── App.tsx # Main orchestrator with tool routing
│ └── index.html # HTML entry (loads frontend.tsx)
├── test/ # Unit test setup and shared mocks
│ ├── setup.ts # Test environment config (jsdom, mocks)
│ └── matchers.d.ts # TypeScript types for test matchers
├── vitest.config.ts # Vitest test configuration
├── e2e/ # End-to-end tests (Playwright)
│ └── fixtures/ # Test assets (images, etc.)
├── build.ts # Custom Bun build script (NOT standard bundler)
├── playwright.config.ts # Playwright E2E test configuration
├── dist/ # Build output (Cloudflare Workers assets)
├── styles/globals.css # Tailwind v4 config (CSS-first, not JS config)
└── wrangler.jsonc # Cloudflare Workers deployment config
| Task | Location | Notes |
|---|---|---|
| Add new tool | src/App.tsx tools array |
Register in array, add route in render |
| Modify theme/settings | src/lib/settings.tsx |
React Context with localStorage |
| Add utility function | src/lib/utils.ts |
Central utilities (cn for styling) |
| File save operations | src/lib/fileSystem.ts |
File System Access API abstraction |
| Add UI component | src/components/ui/ |
shadcn/ui patterns (see ui/AGENTS.md) |
| Build configuration | build.ts |
Custom script, not vite.config |
| Linting/formatting | biome.jsonc |
Biome (not ESLint/Prettier) |
| Unit test configuration | vitest.config.ts |
Vitest with jsdom environment |
| Unit test setup | test/setup.ts |
DOM mocks, matcher extensions |
| E2E test setup | playwright.config.ts |
Browser configs, webServer |
Code Style (STRICT):
- TABS for indentation (not spaces—enforced by Biome)
- No
anytypes (use proper types orunknown) - Double quotes for strings
- Import alias:
@/*maps to./src/* - Strict TypeScript: noUncheckedIndexedAccess enabled (array access returns
T | undefined)
Architecture:
- No backend: Pure client-side SPA
- State management: React Context (settings) + useState (component state)
- Cross-component communication:
window.addGeneratedImagecallback pattern - Persistent storage: localStorage (settings), IndexedDB (directory handles)
- Component patterns: Functional components with hooks (no classes)
Security:
- SVG sanitization: DOMPurify before rendering
- URL validation: Whitelist https/http/data/blob protocols
- Filename sanitization: Strip path separators, control chars, normalize Unicode
- MIME type validation: Whitelist raster formats only
From Biome config:
- ❌ No
anytypes (error: noExplicitAny) - ❌ No CommonJS (error: noCommonJs) - ESM only
- ❌ No
var(error: noVar) - use const/let - ❌ No TypeScript namespaces (error: noNamespace)
From TypeScript config:
- ❌ No type suppression (
as any,@ts-ignore) - ✅ Check array access (always handle
| undefineddue to noUncheckedIndexedAccess)
From security patterns:
- ❌ Never commit API keys, .env secrets, credentials
- ❌ Never trust user input - validate URLs, filenames, MIME types
- ❌ Never render unsanitized SVG - use DOMPurify
Two-Tier Component Architecture:
- UI Primitives (
src/components/ui/) - shadcn/ui vendor code, minimal JSDoc, namespace imports - Feature Components (
src/components/) - application logic, extensive JSDoc, named imports
Different conventions by layer:
| Layer | Import Style | Export Style | JSDoc |
|---|---|---|---|
| UI primitives | import * as React |
End-of-file | None (vendor) |
| Features | import { useState } |
Inline | Extensive |
| Lib | import { x } |
Inline | Comprehensive |
Cloudflare Workers Patterns:
- Static assets in
src/root (notpublic/) - Custom build.ts (not standard bundler config)
- SPA routing via wrangler.jsonc
Modern React:
- React 19 (no React import needed for JSX)
- Hooks-based (useState, useCallback, useEffect, useRef, useMemo)
- No HOCs, no render props, no class components
Tailwind v4:
- CSS-first approach (config in styles/globals.css, not tailwind.config.js)
- OKLCH color space (modern)
- Custom dark mode variant:
:is(.dark *)
Testing:
- Unit test framework: Vitest with jsdom environment
- Component tests: @testing-library/react + @testing-library/user-event
- E2E tests: Playwright (chromium, firefox, webkit)
- Test location: Colocated (
.test.tsnext to source files) - Coverage: v8 provider with text + lcov reporters to
./coverage - Mocked APIs: ResizeObserver, matchMedia, Canvas API, IndexedDB
- NO TESTS for:
src/components/ui/(shadcn/ui vendor code) - Skipped tests: 9 PngConverter conversion flow tests (Image API async limitations in jsdom; fully tested in E2E)
# Development
bun dev # Serve index.html with hot reload
bun start # Local production mode
# Production
bun run build # Build to dist/ via custom build.ts
bun run deploy # Build + deploy to Cloudflare Workers
# Code Quality
bun run lint # Biome check (linter + formatter)
bun run lint:ci # CI mode with GitHub reporter
bun run fmt # Format code with Biome
# Testing
bun run test # Run unit tests (Vitest + jsdom)
bun run test:watch # Watch mode for unit tests
bun run test:ui # Vitest UI mode (browser interface)
bun run test:coverage # Run tests with coverage report
bun run test:e2e # Run E2E tests (Playwright)
bun run test:e2e:ui # Run E2E tests in UI mode (Playwright)
# Dependencies
bun install # Install dependencies (uses bun.lock)Critical Issues:
- No git hooks: Linting only enforced in CI, not pre-commit.
Notable Quirks:
- Service worker in src/ as .js (only JS file in TypeScript project)
- Separate styles/ directory at root (breaks convention of everything in src/)
- Assets (icons, manifest) in src/ root instead of public/
- "use client" directives present despite being pure client-side SPA (SSR-ready architecture)
Browser APIs Used:
- Canvas API (image manipulation)
- File System Access API (directory picker, batch save)
- IndexedDB (cache directory handles)
- localStorage (user settings)
- Service Worker (offline support)
Key Dependencies:
- React 19, Bun runtime
- Radix UI primitives, shadcn/ui patterns
- Tailwind CSS v4, Biome toolchain
- gifuct-js (GIF parsing), DOMPurify (SVG sanitization)
Architectural Decisions:
- No state management library (plain Context + hooks)
- No router (conditional rendering)
- No backend/API layer
- Window callback pattern for cross-component communication
- Two-tier component system (UI primitives vs features)