diff --git a/.agents/skills/testing-lighthouse/SKILL.md b/.agents/skills/testing-lighthouse/SKILL.md index 529f7169..2c7c99dd 100644 --- a/.agents/skills/testing-lighthouse/SKILL.md +++ b/.agents/skills/testing-lighthouse/SKILL.md @@ -9,6 +9,49 @@ The Lighthouse Scanner lives at `/lighthouse` in the Next.js app (`apps/web`). I - **Cloudflare Pages preview**: Every PR gets a preview deploy. Check the PR comments from `cloudflare-workers-and-pages[bot]` for the preview URL (format: `https://.dba-92r.pages.dev`). - **Production**: `https://designedbyanthony.com/lighthouse` +## Important: Pages vs Worker Deployment + +This repo has **two separate deploy targets**: +- **Cloudflare Pages** (`apps/web`) — deploys automatically via PR/merge CI +- **Cloudflare Worker** (`apps/api`) — deploys separately via `wrangler deploy` from `apps/api/` + +Cloudflare Pages preview URLs only include frontend changes. **API Worker changes (routes in `apps/api/src/routes/`) are NOT included in Pages previews.** To test API changes, use local `wrangler dev` (see below) or wait for a separate Worker deployment. + +## Testing API Routes Locally + +To test API Worker routes (e.g., `/api/report/:id/pdf`, `/api/audit`, `/api/audit/email-summary`): + +1. **Start the API locally:** + ```bash + cd apps/api && npx wrangler dev --local + ``` + This starts the Worker on `http://localhost:8788` with local KV. + +2. **Seed test data into local KV** (needed for endpoints that read from the report store): + ```bash + cd apps/api && npx wrangler kv key put "DBA-TEST1234" \ + --binding AUDIT_REPORTS_KV --local \ + "$(cat /path/to/test-report.json)" + ``` + The test report JSON must match the full stored payload structure from `audit.ts` — including nested `sitewide.robotsTxt.exists`, `sitewide.sitemap.exists`, `backlinks.found`, `places.found`, `indexCoverage.found` fields. Empty objects (`{}`) for these fields will cause `buildAuditPdf` to crash with "Cannot read properties of undefined". + +3. **Verify with curl:** + ```bash + # Health check + curl -s http://localhost:8788/health + + # Test PDF headers + curl -sD - http://localhost:8788/api/report/DBA-TEST1234/pdf -o /tmp/test.pdf + + # Test CORS preflight + curl -sD - -X OPTIONS http://localhost:8788/api/audit/email-summary \ + -H "Origin: https://designedbyanthony.com" -o /dev/null + ``` + +## Elysia Response Pattern + +When testing Elysia routes that return raw `Response` objects: in Elysia, `set.headers` is ignored when returning a raw `Response`. Headers must be passed via the `Response` constructor. If you see missing headers on a raw Response return, check for this pattern mismatch. Routes that return plain objects (JSON) can use `set.headers` normally. + ## Key Visual Elements to Verify When testing Lighthouse UI changes, compare against the main site homepage (`/`) for consistency: @@ -33,6 +76,7 @@ When testing Lighthouse UI changes, compare against the main site homepage (`/`) - **CSS class name mismatches**: When refactoring CSS class names, always check that components still reference the correct names. Use `grep -oP 'className="[^"]*lh-[^"]*"' .tsx | grep -oP 'lh-[a-z0-9_-]+'` to extract class names from components and verify they exist in the CSS file. - **Local dev 500 errors**: The homepage might 500 locally due to missing env vars, but `/lighthouse` usually works. Use the Cloudflare Pages preview URL as a reliable alternative. - **Bronze is intentional in some places**: The submit button CTA and decorative accent rules intentionally keep bronze — don't flag these as bugs. +- **Empty nested objects in test data**: When seeding test reports for PDF generation, ensure all nested objects (`sitewide`, `backlinks`, `places`, `indexCoverage`) have their full expected structure. `buildAuditPdf` accesses nested properties like `sitewide.robotsTxt.exists` and will crash on empty objects. ## Devin Secrets Needed diff --git a/STATUS.md b/STATUS.md index 9a0842d8..d8a5c61e 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,14 +2,23 @@ > **Note:** This file tracks migration and release notes for the **Turborepo Cloudflare** app (`apps/web` Next.js Pages + `apps/api` Elysia Worker, `bun`, `bun.lock`). Older single-app / Astro-era detail is archived below for context — see [README.md](README.md) and [AGENTS.md](AGENTS.md). +## Codebase cleanup, perf optimization & tech integrations (2026-04-30) + +- Removed Zustand badge from the "Our Edge" stack display — dependency was previously removed but badge remained. +- Removed stray `console.warn` in the competitor-scan catch block of `apps/api/src/routes/audit.ts`. +- Cleaned up outdated Firebase/Firestore references in `lighthouse2.md` (now reflects Cloudflare Pages/Workers + KV/D1). +- Added `manifest.webmanifest` to `apps/web/public/` with PWA metadata — triggers PWA detection on Wappalyzer/BuiltWith. +- Added JSON-LD structured data (`Organization`, `LocalBusiness`, `WebSite`) in root layout — zero render cost, boosts SEO. +- Verification: `bun run lint`, `bun run build` pass from the repo root. + ## Lockfile fix + backend PDF offload (2026-04-30) + - **Lockfile fix (P0):** Removed conflicting workspace-level `overrides` from `apps/web/package.json` (stale `postcss ^8.4.38` vs root `^8.5.12`, duplicate `uuid`, unrecorded `yaml@<2.8.3`). Moved `yaml@<2.8.3` override to root `package.json`. Regenerated `bun.lock` — `bun install --frozen-lockfile` now passes in CI. - **Dependency hygiene:** Relocated heavy backend-only packages (`@google/genai`, `googleapis`, `cheerio`, `jspdf`) from `apps/web` to `packages/shared` where they are actually imported. Frontend bundle no longer ships these libraries. - **PDF offload to API Worker:** Created `GET /api/report/:id/pdf` on the ElysiaJS Worker (`apps/api/src/routes/reportPdf.ts`). The route fetches the persisted report from the KV store, calls `buildAuditPdf` server-side, and streams the PDF back with `Content-Disposition: attachment`. `AuditResults.tsx` now fetches from this endpoint instead of importing `jspdf` client-side — removes ~250 KB from the browser bundle and eliminates client-side PDF generation latency. - Added `./lighthouse/*` export to `packages/shared` so `AuditData` types are accessible from any workspace. - Verification: `bun install --frozen-lockfile`, `bun run build`, `bun run typecheck`, and `bun run lint` pass from the repo root. - ## Micro SaaS store /tools page build-out (2026-04-30) - Replaced the "Coming Soon" waitlist on `/tools` with a full product catalog showcasing 6 Stripe-backed micro-SaaS products: SiteScan, ReviewPilot, ClientHub, LocalRank, TestiFlow, and ContentMill. diff --git a/apps/api/src/routes/audit.ts b/apps/api/src/routes/audit.ts index a7d5742a..6d36c1c4 100644 --- a/apps/api/src/routes/audit.ts +++ b/apps/api/src/routes/audit.ts @@ -282,8 +282,8 @@ export const auditRoute = new Elysia({ aot: false }).post( placesData.primaryType, 3, ); - } catch (compErr) { - console.warn("Competitor scan failed:", compErr); + } catch { + /* competitor scan is best-effort — swallow errors silently */ } const categories = lighthouse.categories; diff --git a/apps/web/public/manifest.webmanifest b/apps/web/public/manifest.webmanifest new file mode 100644 index 00000000..7d240ebe --- /dev/null +++ b/apps/web/public/manifest.webmanifest @@ -0,0 +1,26 @@ +{ + "name": "Designed by Anthony", + "short_name": "DBA", + "description": "Custom web design and local SEO for service businesses in the Mohawk Valley and Central New York.", + "start_url": "/", + "display": "standalone", + "background_color": "#0f1218", + "theme_color": "#0f1218", + "icons": [ + { + "src": "/site-icon-48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "/site-icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/site-icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 02f4bd84..76dbd286 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,5 +1,6 @@ import "@/design-system/dba-global.css"; import { CrispBootstrap } from "@/components/CrispBootstrap"; +import { JsonLd } from "@/components/JsonLd"; import "@/styles/layout-shell.css"; import type { Metadata, Viewport } from "next"; import type { ReactNode } from "react"; @@ -25,6 +26,7 @@ export const metadata: Metadata = { }, description: "Custom web design and local SEO for service businesses in the Mohawk Valley and Central New York.", + manifest: "/manifest.webmanifest", appleWebApp: { capable: true, statusBarStyle: "black-translucent", @@ -77,6 +79,9 @@ export default function RootLayout({ children }: { children: ReactNode }) { data-scroll-behavior="smooth" data-lead-webhook={leadWebhookDefault || undefined} > + + + {/* Google Tag Manager (noscript) */}