diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c905d..0d13bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main, dev] + branches: [production, dev] pull_request: - branches: [main, dev] + branches: [production, dev] jobs: build: diff --git a/.github/workflows/pr-target-check.yml b/.github/workflows/pr-target-check.yml index 3ef2504..7dde21a 100644 --- a/.github/workflows/pr-target-check.yml +++ b/.github/workflows/pr-target-check.yml @@ -2,16 +2,16 @@ name: PR Target Check on: pull_request: - branches: [main] + branches: [production] jobs: check-source-branch: runs-on: ubuntu-latest steps: - - name: Only allow PRs from dev to main + - name: Only allow PRs from dev to production if: github.head_ref != 'dev' run: | - echo "::error::PRs targeting 'main' are only allowed from the 'dev' branch." + echo "::error::PRs targeting 'production' are only allowed from the 'dev' branch." echo "Please target 'dev' instead, or merge your branch into 'dev' first." echo "" echo " Source: ${{ github.head_ref }}" @@ -19,4 +19,4 @@ jobs: exit 1 - name: PR source branch is valid if: github.head_ref == 'dev' - run: echo "PR from 'dev' to 'main' — allowed." + run: echo "PR from 'dev' to 'production' — allowed." diff --git a/CLAUDE.md b/CLAUDE.md index b14de89..03c7366 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,16 +1,21 @@ # CLAUDE.md — OGCOPS +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + ## What is this? -OGCOPS is a free, open-source OG image generator and social media preview checker. Built with Astro + React Islands, deployed to Vercel. +OGCOPS is a free, open-source OG image generator and social media preview checker. Built with Astro SSR + React Islands, deployed to Vercel at og.codercops.com. GitHub: github.com/codercops/ogcops. MIT licensed. ## Commands ```bash -npm run dev # Start dev server -npm run build # Production build -npm run preview # Preview production build -npm run test # Run vitest -npm run test:watch # Watch mode -npm run check # Type-check (astro check + tsc) +npm run dev # Start dev server (port 4321) +npm run build # Production build +npm run preview # Preview production build +npm run test # Run vitest (single run) +npm run test:watch # Watch mode +npm run test:ui # Vitest UI +npm run check # Type-check (astro check + tsc --noEmit) +npm run lint # Astro linting +npm run generate:favicons # Generate favicon assets ``` ## Architecture @@ -19,20 +24,32 @@ npm run check # Type-check (astro check + tsc) - **Satori** runs client-side for instant SVG preview (zero server calls during editing) - **Satori + resvg-wasm** runs server-side for PNG generation (`/api/og`) - **No database** — state lives in URL query params + client-side useReducer -- **CORS-open API** for developer use +- **CORS-open API** — no API key, no rate limits ## Key Directories -- `src/templates/` — 109 templates across 12 categories. Each template is a `.ts` file exporting a `TemplateDefinition`. -- `src/lib/` — Core engine (og-engine.ts, font-loader.ts, meta-fetcher.ts, meta-analyzer.ts) +- `src/templates/` — 120 templates across 12 categories. Each is a `.ts` file exporting a `TemplateDefinition`. +- `src/lib/` — Core engine (og-engine.ts, font-loader.ts, meta-fetcher.ts, meta-analyzer.ts, api-validation.ts) - `src/components/editor/` — React components for the OG image editor - `src/components/preview/` — React components for the social media preview checker -- `src/pages/api/` — API endpoints (og, preview, templates) +- `src/pages/api/` — API endpoints +- `src/styles/` — CSS files (global.css, editor.css, preview.css, api-docs.css) +- `public/fonts/` — Bundled .woff fonts (Inter, Playfair Display, JetBrains Mono) +- `tests/` — Vitest tests (api/, lib/, templates/) -## Conventions -- CSS custom properties only (no Tailwind). Accent: `#E07A5F`. -- TypeScript strict mode. Path alias `@/*` → `src/*`. -- Fonts bundled as `.woff` in `public/fonts/`. -- Templates follow `TemplateDefinition` interface in `src/templates/types.ts`. +## Pages & API Endpoints + +**Pages:** +- `/` — Homepage +- `/create/` — OG image editor +- `/templates` — Template gallery +- `/preview` — Social media preview checker +- `/api-docs` — API documentation + +**API (all CORS-open, no auth):** +- `GET /api/og?template={id}&...` — Generate PNG (1200x630, 24h cache) +- `GET /api/preview?url={url}` — Fetch and analyze a URL's meta tags +- `GET /api/templates` — List all templates as JSON +- `GET /api/templates/[id]/thumbnail.png` — Template thumbnail (1-week cache) ## Reference Files - Template interface: `src/templates/types.ts` @@ -42,12 +59,34 @@ npm run check # Type-check (astro check + tsc) - API validation schemas: `src/lib/api-validation.ts` - Template registry: `src/templates/registry.ts` -## Template Contribution -1. Create `src/templates/{category}/{id}.ts` -2. Export a `TemplateDefinition` -3. Register in `src/templates/{category}/index.ts` +## Template System + +120 templates across 12 categories: blog, product, saas, github, event, podcast, developer, newsletter, quote, ecommerce, job, tutorial. + +**Adding a template:** +1. Create `src/templates/{category}/{id}.ts` exporting a `TemplateDefinition` +2. Register in `src/templates/{category}/index.ts` +3. Add import + registration in `src/templates/registry.ts` 4. Run `npm run test` to verify +**Template field types:** text, textarea, color, select, number, toggle, image. +**Field groups:** Content, Style, Brand. + +## Conventions +- CSS custom properties only (no Tailwind). Accent: `#E07A5F`. +- TypeScript strict mode. Path alias `@/*` → `src/*`. +- Fonts bundled as `.woff` in `public/fonts/` (Inter Regular/Medium/SemiBold/Bold, Playfair Display Regular/Bold, JetBrains Mono Regular/Bold). +- Node 22 (`.nvmrc`). +- Testing: Vitest with node environment. Tests in `tests/**/*.test.ts`. Globals enabled. + +## CI/CD +- Runs on push to `production`/`dev` and PRs to those branches +- Steps: `npm run check` → `npm run test` → `npm run build` +- **Branch strategy:** `dev` is the default branch. PRs target `dev`. Releases go `dev` → `production`. Direct PRs to `production` are blocked unless from `dev`. + +## Environment Variables (`.env.local`, all optional) +- `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN` — Optional visitor counter + ## Gotchas / Constraints - Satori does **not** support CSS grid — only flexbox - Every `div` must have `display: 'flex'` explicitly in its style @@ -55,6 +94,7 @@ npm run check # Type-check (astro check + tsc) - Font files must be listed in `astro.config.mjs` `includeFiles` array for Vercel deployment - WASM imports need `optimizeDeps.exclude` in the Vite config - `renderToPng` returns `ArrayBuffer` (not `Buffer`) for `BodyInit` compatibility +- Canvas is always 1200x630px ## Do NOT - Add Tailwind CSS — the project uses CSS custom properties only diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b228e9..e912fb4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,7 +113,7 @@ npm run build # Full production build 1. Fork the repo and create a branch: `git checkout -b feat/my-feature` 2. Make your changes 3. Ensure `npm run check` and `npm run test` pass -4. Push and open a PR against `main` +4. Push and open a PR against `dev` 5. Fill out the [PR template](.github/PULL_REQUEST_TEMPLATE.md) — screenshots are required for visual changes 6. Wait for review diff --git a/README.md b/README.md index 4ca02ab..19b6b1d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ OGCOPS is different: ## Quick Start ```bash -git clone https://github.com/codercops/ogcops.git +git clone -b dev https://github.com/codercops/ogcops.git cd ogcops npm install npm run dev @@ -114,6 +114,16 @@ The build output in `dist/` can be deployed to any Node.js hosting platform. Contributions are welcome — templates, bug fixes, features, docs, and more. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines. +> **Important:** Always fork and branch from `dev` (the default branch). The `production` branch is for releases only. PRs targeting `production` directly will be closed. + +```bash +# Fork the repo on GitHub, then: +git clone https://github.com//ogcops.git +cd ogcops +git checkout dev +git checkout -b your-feature-branch +``` + - [Open an issue](https://github.com/codercops/ogcops/issues) — bug reports and feature requests - [Start a discussion](https://github.com/codercops/ogcops/discussions) — questions, ideas, show & tell diff --git a/SECURITY.md b/SECURITY.md index 64fdfe5..a12dbb6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -35,7 +35,7 @@ The following are **out of scope:** | Version | Supported | |---------|-----------| -| Latest (main branch) | Yes | +| Latest (production branch) | Yes | | Older releases | No | ## Recognition diff --git a/src/components/Footer.astro b/src/components/Footer.astro index b11e817..805026b 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -20,6 +20,7 @@ const year = new Date().getFullYear(); Templates API Docs GitHub + Feedback diff --git a/src/components/Header.astro b/src/components/Header.astro index 086b61a..7ae7f71 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -36,41 +36,82 @@ const navItems = [ - - - -
- + diff --git a/src/components/editor/EditorApp.tsx b/src/components/editor/EditorApp.tsx index e8f9035..b992b98 100644 --- a/src/components/editor/EditorApp.tsx +++ b/src/components/editor/EditorApp.tsx @@ -31,6 +31,17 @@ export function EditorApp({ initialCategory }: EditorAppProps) { const { svg, loading, error, render } = useSatoriRenderer(); const [mobileTab, setMobileTab] = useState<'templates' | 'customize' | 'export'>('customize'); + const [isMobile, setIsMobile] = useState(() => + typeof window !== 'undefined' ? window.innerWidth <= 768 : false + ); + + useEffect(() => { + const mq = window.matchMedia('(max-width: 768px)'); + const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches); + mq.addEventListener('change', handler); + setIsMobile(mq.matches); + return () => mq.removeEventListener('change', handler); + }, []); // Find current template definition (use registry lookup to ensure render function is present) const currentTemplate = useMemo( @@ -59,8 +70,11 @@ export function EditorApp({ initialCategory }: EditorAppProps) { const handleTemplateSelect = useCallback( (template: TemplateDefinition) => { setTemplate(template); + if (isMobile) { + setMobileTab('customize'); + } }, - [setTemplate] + [setTemplate, isMobile] ); const handleReset = useCallback(() => { @@ -114,11 +128,20 @@ export function EditorApp({ initialCategory }: EditorAppProps) { />
- {/* Right: Customize */} + {/* Right: Customize / Export */}
{mobileTab === 'export' ? (
+
+ +
+
) : ( copyToClipboard(apiUrl, 'url')} > - {copied === 'url' ? 'Copied!' : 'Copy URL'} + + {copied === 'url' ? 'Copied!' : 'Copy Image URL'}
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 0cb1229..1d0ce60 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -31,7 +31,7 @@ const fullTitle = title === 'OGCOPS' ? title : `${title} | ${siteName}`; - + {noindex && } @@ -106,7 +106,7 @@ const fullTitle = title === 'OGCOPS' ? title : `${title} | ${siteName}`; -
+
diff --git a/src/layouts/ToolLayout.astro b/src/layouts/ToolLayout.astro index 1c21182..37acc6d 100644 --- a/src/layouts/ToolLayout.astro +++ b/src/layouts/ToolLayout.astro @@ -19,7 +19,7 @@ const fullTitle = `${title} | ${siteName}`; - + @@ -61,7 +61,14 @@ const fullTitle = `${title} | ${siteName}`; body { display: flex; flex-direction: column; + min-height: 100vh; + min-height: 100dvh; height: 100vh; + height: 100dvh; overflow: hidden; + padding-top: env(safe-area-inset-top); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); + padding-bottom: env(safe-area-inset-bottom); } diff --git a/src/pages/api-docs.astro b/src/pages/api-docs.astro index 9c56591..591db5b 100644 --- a/src/pages/api-docs.astro +++ b/src/pages/api-docs.astro @@ -5,24 +5,29 @@ import '@/styles/api-docs.css';
- + +
+

API Documentation

+

+ Generate OG images, check URL meta tags, and list templates with our free REST API. + No API key required. CORS-enabled for browser use. +

+
+ +