Skip to content

Instantly share code, notes, and snippets.

@loveJesus
Last active January 2, 2026 00:59
Show Gist options
  • Select an option

  • Save loveJesus/aa578e0b92131c61580bed15a4259c53 to your computer and use it in GitHub Desktop.

Select an option

Save loveJesus/aa578e0b92131c61580bed15a4259c53 to your computer and use it in GitHub Desktop.
FaithStack Master Spec v2.0 — AI Agent Instructions, Testing, Feedback, Audit & Best Practices

For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life. — John 3:16

FaithStack Master Spec (v2.0)

"Whatever you do, work at it with all your heart, as working for the Lord." — Colossians 3:23

This repository contains the complete specification for building FaithStack projects. Copy these files into your project's spec_chirho/ directory, or reference them as your AI agent's instructions.


Files in This Gist

File Purpose Copy Into
00-README_CHIRHO.md This file - overview and instructions
01-AGENTS_MASTER_CHIRHO.md Complete AI agent instructions (13 sections) AGENTS.md
02-TESTING_CHIRHO.md Solo dev testing guide (fast, headless) spec_chirho/TESTING_CHIRHO.md
03-COMMUNITY_FEEDBACK_CHIRHO.md Feedback, tickets, Q&A, surveys, KB spec_chirho/FEEDBACK_CHIRHO.md
04-DATABASE_AUDIT_CHIRHO.md Audit pipeline for D1/R2 spec_chirho/AUDIT_CHIRHO.md
05-BEST_PRACTICES_CHIRHO.md Legal, security, compliance (20 sections) spec_chirho/BEST_PRACTICES_CHIRHO.md

CLAUDE.md should just contain: @AGENTS.md


Quick Start

New Project

  1. Create your project directory
  2. Copy 01-AGENTS_MASTER_CHIRHO.md to AGENTS.md (or CLAUDE.md)
  3. Create spec_chirho/ directory
  4. Copy relevant guides into spec_chirho/
  5. Initialize git: git init && git add . && git commit -m "Initial commit — JESUS CHRIST IS LORD"

Existing Project

  1. Add AGENTS.md with relevant sections from this spec
  2. Create spec_chirho/ if missing
  3. Add checklists from the guides to track implementation

What Makes a FaithStack Project?

A FaithStack project:

  1. Glorifies God through excellent stewardship
  2. Serves users with high-quality, accessible software
  3. Sustains ministry through sustainable revenue
  4. Uses Chirho conventions (_chirho suffix for our code)
  5. Is spec-drivenspec_chirho/ is the source of truth
  6. Has AI monitoring — feedback systems with escalation

Tech Stack

Layer Technology
Runtime Cloudflare Workers
Framework SvelteKit (recommended)
Database D1 (SQLite)
Storage R2
Cache KV
Email 2SMTP / Mailu
Payments Stripe
Package Manager Bun (NEVER npm/yarn/pnpm)

Key Principles

  1. Spec is Truth — YAML schemas in spec_chirho/01_DATA_MODEL_CHIRHO/ define everything
  2. Chirho Suffix — Our code uses _chirho suffix to distinguish from third-party
  3. Git Authority[AI-CHIRHO] commits indicate AI-assisted work
  4. AI Monitoring — Every project has feedback → ticket → human escalation
  5. Test What Matters — Money paths, auth, core APIs (<30 seconds total)
  6. Database Auditing — All mutations logged to private R2 bucket

Live Endpoints

Guide URL
Best Practices https://orchestrate-chirho.perffection.com/guide-7316-fe
Testing Guide https://orchestrate-chirho.perffection.com/testing-7316-fe
Feedback & Audit https://orchestrate-chirho.perffection.com/feedback-audit-7316-fe

Version History

  • v2.0 (2025-12-29) — Combined master spec with testing, feedback, and audit guides
  • v1.0 (2025-12-01) — Initial 20-section spec-driven development framework

JESUS CHRIST IS LORD

For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life. — John 3:16

FaithStack AI Agent Instructions (v2.0)

The Gospel: Jesus Christ, the Son of God, died for our sins, was buried, and rose again on the third day according to the Scriptures. Whoever believes in Him shall not perish but have eternal life. (1 Corinthians 15:3-4, John 3:16)


Section 1: Prime Directive

YOU ARE A SPEC-DRIVEN AI AGENT.

Your job is to implement exactly what is specified, no more, no less. The specification (spec_chirho/) is your single source of truth. When unclear, ASK the human rather than guess.

Critical Rules

  1. SPEC FIRST — Read spec_chirho/ before writing any code
  2. ASK IF UNCLEAR — Never assume. A wrong assumption wastes time.
  3. CHIRHO SUFFIXALL our identifiers use _chirho, -chirho, or Chirho suffix
  4. BUN ONLY — Use bun, bunx. NEVER npm, npx, yarn, pnpm.
  5. MATCH PATTERNS — Look at existing code first. Match its style.
  6. GIT AUTHORITY — Uncommitted changes need human confirmation
  7. NO SECRETS IN GIT — NEVER put API keys in wrangler.toml or code
  8. LATEST VERSIONS — Use latest wrangler, adapter-cloudflare (NOT pages)

Section 2: Project Setup Workflow

New Project Setup

  1. Create CLAUDE.md pointing to AGENTS.md:

    @AGENTS.md
  2. Copy environment file:

    cp ~/.env-chirho .env
  3. Initialize with latest wrangler:

    bunx wrangler@latest init project-name-chirho
    # Choose: Cloudflare Workers (NOT Pages)
    # Use adapter-cloudflare, NOT adapter-cloudflare-pages
  4. Create spec directory structure:

    mkdir -p spec-chirho/{01-data-model-chirho,templates-chirho,references-chirho,flows-chirho}
  5. Copy AGENTS-MASTER-CHIRHO.md to AGENTS.md (customize for project)

  6. Set secrets via wrangler (NEVER in wrangler.toml):

    bunx wrangler secret put MASTER_2SMTP_API_KEY_CHIRHO
    bunx wrangler secret put STRIPE_SECRET_KEY_CHIRHO
    bunx wrangler secret put TURNSTILE_SECRET_KEY_CHIRHO

Section 3: COMPREHENSIVE Identifier Suffixing

CRITICAL: EVERY identifier we create MUST have the chirho suffix in appropriate case.

Complete Suffix Rules

Type Case Style Suffix Example
Variables/Functions/Properties/Parameters/Consts (TS) camelCase Chirho userDataChirho, fetchProductsChirho()
Classes/Components/Types/Interfaces PascalCase Chirho ProductCardChirho, EventTypeChirho
Constants/Env Vars SCREAMING_SNAKE _CHIRHO API_KEY_CHIRHO, STRIPE_KEY_CHIRHO
Directories kebab-case -chirho routes-chirho/, lib-chirho/
Files kebab-case -chirho user-auth-chirho.ts, stripe-chirho.ts
Web Routes kebab-case -chirho /products-chirho, /api-chirho/users-chirho
Public Routes kebab-case -fe /login-fe, /dashboard-fe
Database Tables snake_case _chirho products_chirho, events_chirho
Database Columns snake_case _chirho price_in_cents_chirho, customer_email_chirho
CSS Classes kebab-case -chirho .hero-chirho, .button-chirho
package.json scripts kebab-case -chirho dev-chirho, db-migrate-chirho
KV Keys kebab-case:kebab-case -chirho users-chirho:id1-chirho, session-chirho:abc123-chirho
JSON API Response Props camelCase Chirho successChirho, productChirho, orderNumberChirho
R2 Buckets kebab-case -chirho audit-logs-chirho, uploads-chirho
Wrangler Bindings SCREAMING_SNAKE _CHIRHO DB_CHIRHO, FEEDBACK_KV_CHIRHO

Examples in Context

// ✅ CORRECT - All identifiers suffixed appropriately

// Types use PascalCase + Chirho
interface UserChirho {
  idChirho: string;
  nameChirho: string;
  emailChirho: string;
}

// Constants use SCREAMING_SNAKE + _CHIRHO
const MAX_RETRIES_CHIRHO = 3;

// Functions, variables, parameters use camelCase + Chirho
async function fetchUsersChirho(limitChirho: number): Promise<UserChirho[]> {
  const usersChirho: UserChirho[] = [];

  try {
    const responseChirho = await fetch('/api-chirho/users-chirho');
    const dataChirho = await responseChirho.json();

    // Loop variables use camelCase + Chirho
    for (const userChirho of dataChirho.resultsChirho) {
      const { idChirho, nameChirho } = userChirho;
      usersChirho.push({ idChirho, nameChirho, emailChirho: userChirho.emailChirho });
    }

    // Lambda variables use camelCase + Chirho
    return usersChirho.filter((uChirho) => uChirho.nameChirho !== null);
  } catch (errorChirho) {
    console.error('Failed:', errorChirho);
    throw errorChirho;
  }
}

// Object properties in arrays use camelCase + Chirho
const configsChirho = [
  { keyChirho: 'theme', valueChirho: 'dark' },
  { keyChirho: 'lang', valueChirho: 'en' }
];

configsChirho.forEach((configChirho) => {
  console.log(configChirho.keyChirho, configChirho.valueChirho);
});

// KV key pattern: type-chirho:instance-chirho
await env.USERS_KV_CHIRHO.put('session-chirho:abc123-chirho', JSON.stringify(sessionChirho));

Database Columns (snake_case exception)

-- DB columns use snake_case + _chirho (SQL convention)
CREATE TABLE users_chirho (
  id_chirho TEXT PRIMARY KEY,
  email_chirho TEXT NOT NULL,
  created_at_chirho INTEGER NOT NULL
);

package.json Scripts

{
  "scripts": {
    "dev-chirho": "vite dev",
    "build-chirho": "vite build",
    "preview-chirho": "vite preview",
    "test-chirho": "vitest run",
    "test-e2e-chirho": "playwright test",
    "deploy-chirho": "bun run test-chirho && bunx wrangler deploy",
    "db-migrate-chirho": "bunx wrangler d1 migrations apply DB_CHIRHO",
    "db-studio-chirho": "bunx drizzle-kit studio",
    "generate-types-chirho": "bun run scripts_chirho/generate-types-chirho.ts",
    "check-chirho": "svelte-kit sync && svelte-check"
  }
}

What NOT to Suffix

  • Framework files: +page.svelte, +server.ts, vite.config.ts, package.json
  • Framework directories: src/, routes/, lib/, static/, node_modules/
  • Third-party imports: import { redirect } from '@sveltejs/kit'
  • Framework functions: json(), error(), redirect(), fetch()
  • Node/Bun globals: console, process, crypto, Request, Response
  • Standard properties: id, name, email when interfacing with external APIs
  • HTML attributes: class, id, href, src (but values can have -chirho)

Section 4: Secrets Management

NEVER Put Secrets In:

  • wrangler.toml
  • Source code
  • Git-tracked files
  • Comments or documentation

Where Secrets Go:

  1. Local development: .env (git-ignored)

    # .env (copy from ~/.env-chirho)
    MASTER_2SMTP_API_KEY_CHIRHO=mk_...
    STRIPE_SECRET_KEY_CHIRHO=sk_live_...
    TURNSTILE_SECRET_KEY_CHIRHO=0x...
    CLOUDFLARE_API_TOKEN_CHIRHO=...
  2. Production: Wrangler secrets

    bunx wrangler secret put MASTER_2SMTP_API_KEY_CHIRHO
    bunx wrangler secret put STRIPE_SECRET_KEY_CHIRHO
    bunx wrangler secret put TURNSTILE_SECRET_KEY_CHIRHO
  3. Document in .env.example:

    # .env.example (git-tracked, no values)
    MASTER_2SMTP_API_KEY_CHIRHO=
    STRIPE_SECRET_KEY_CHIRHO=
    TURNSTILE_SECRET_KEY_CHIRHO=

.gitignore Must Include:

.env
.env.local
.env.*
*.env
env_available_chirho
.dev.vars

Section 5: Required Footer

Every page footer MUST include:

<footer class="footer-chirho">
  <nav class="footer-links-chirho">
    <a href="/privacy-fe">Privacy Policy</a>
    <a href="/terms-fe">Terms of Service</a>
    <a href="/contact-fe">Contact</a>
  </nav>

  <div class="footer-brand-chirho">
    <p>&copy; 2025 Your Company. All rights reserved.</p>
    <p class="footer-faith-chirho">
      <a href="https://perffection.com">fe</a> |
      <a href="https://loveJesus.software">loveJesus</a> |
      <a href="http://jesusfilm.org/watch/jesus.html/english.html"></a>
    </p>
    <p class="footer-lord-chirho">JESUS CHRIST IS LORD</p>
  </div>
</footer>

Section 6: Email Setup (2SMTP + Mailu)

Environment Variables

# ~/.env-chirho - Master key for creating project keys
MASTER_2SMTP_API_KEY_CHIRHO=mk_...

# Per-project keys created via 2SMTP dashboard
PROJECT_2SMTP_API_KEY_CHIRHO=rk_...

Email Sender Pattern

// Suffix emails with .fe
const fromEmailChirho = 'noreply.fe@yourdomain.com';
const supportEmailChirho = 'support.fe@yourdomain.com';

2SMTP API Usage

async function sendEmailChirho(
  toChirho: string[],
  subjectChirho: string,
  bodyHtmlChirho: string
): Promise<boolean> {
  const responseChirho = await fetch('https://2smtp.com/api_fe/send_email_fe', {
    method: 'POST',
    headers: {
      'X-API-Key': env.PROJECT_2SMTP_API_KEY_CHIRHO,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      to_chirho: toChirho,
      subject_chirho: subjectChirho,
      body_html_chirho: bodyHtmlChirho,
      reply_to_chirho: 'support.fe@yourdomain.com'
    })
  });

  return responseChirho.ok;
}

DNS Setup (via Cloudflare)

Required DNS records for email sending:

  • SPF: v=spf1 include:_spf.mailer-aleluya.xjes.us ~all
  • DKIM: Add key from Mailu admin panel
  • DMARC: v=DMARC1; p=quarantine; rua=mailto:dmarc.fe@yourdomain.com

Section 7: Feedback Bubble (Every Page)

Required Components

  1. Svelte Component: FeedbackBalloonChirho.svelte
  2. API Endpoint: /api-chirho/feedback-chirho
  3. KV Namespace: FEEDBACK_KV_CHIRHO
  4. Turnstile Protection: Required for spam prevention
  5. Admin View: /admin-fe/feedback-chirho

Component Implementation

<!-- src/lib/components/FeedbackBalloonChirho.svelte -->
<script lang="ts">
  import { page } from '$app/stores';
  import { onMount } from 'svelte';

  let showFormChirho = false;
  let messageChirho = '';
  let feedbackTypeChirho: 'bug' | 'suggestion' | 'praise' | 'confused' = 'suggestion';
  let turnstileTokenChirho = '';
  let submittingChirho = false;

  async function submitFeedbackChirho() {
    if (!messageChirho.trim() || !turnstileTokenChirho) return;

    submittingChirho = true;

    const feedbackChirho = {
      page_url_chirho: $page.url.pathname,
      feedback_type_chirho: feedbackTypeChirho,
      message_chirho: messageChirho,
      scroll_position_chirho: window.scrollY,
      viewport_chirho: `${window.innerWidth}x${window.innerHeight}`,
      user_agent_chirho: navigator.userAgent,
      timestamp_chirho: new Date().toISOString(),
      turnstile_token_chirho: turnstileTokenChirho
    };

    try {
      await fetch('/api-chirho/feedback-chirho', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(feedbackChirho)
      });

      messageChirho = '';
      showFormChirho = false;
      alert('Thank you for your feedback!');
    } catch (errorChirho) {
      console.error('Feedback error:', errorChirho);
    } finally {
      submittingChirho = false;
    }
  }
</script>

<div class="feedback-balloon-chirho">
  {#if showFormChirho}
    <div class="feedback-form-chirho">
      <select bind:value={feedbackTypeChirho}>
        <option value="bug">🐛 Bug Report</option>
        <option value="suggestion">💡 Suggestion</option>
        <option value="praise">❤️ Praise</option>
        <option value="confused">❓ Confused</option>
      </select>

      <textarea
        bind:value={messageChirho}
        placeholder="Your feedback..."
        rows="4"
      ></textarea>

      <!-- Turnstile widget -->
      <div
        class="cf-turnstile"
        data-sitekey={PUBLIC_TURNSTILE_SITE_KEY_CHIRHO}
        data-callback="onTurnstileSuccess"
      ></div>

      <div class="feedback-actions-chirho">
        <button on:click={() => showFormChirho = false}>Cancel</button>
        <button on:click={submitFeedbackChirho} disabled={submittingChirho}>
          {submittingChirho ? 'Sending...' : 'Send'}
        </button>
      </div>
    </div>
  {:else}
    <button
      class="feedback-trigger-chirho"
      on:click={() => showFormChirho = true}
    >
      💬
    </button>
  {/if}
</div>

API Endpoint

// src/routes/api-chirho/feedback-chirho/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request, platform, locals }) => {
  const feedbackChirho = await request.json();

  // Verify Turnstile token
  const turnstileResponseChirho = await fetch(
    'https://challenges.cloudflare.com/turnstile/v0/siteverify',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        secret: platform.env.TURNSTILE_SECRET_KEY_CHIRHO,
        response: feedbackChirho.turnstile_token_chirho
      })
    }
  );

  const turnstileResultChirho = await turnstileResponseChirho.json();
  if (!turnstileResultChirho.success) {
    throw error(400, 'Turnstile verification failed');
  }

  // Generate ID and store in KV
  const idChirho = crypto.randomUUID();
  const entryChirho = {
    id_chirho: idChirho,
    ...feedbackChirho,
    user_id_chirho: locals.user?.id_chirho || null,
    created_at_chirho: new Date().toISOString(),
    status_chirho: 'new'
  };

  await platform.env.FEEDBACK_KV_CHIRHO.put(
    `feedback:${idChirho}`,
    JSON.stringify(entryChirho)
  );

  return json({ success_chirho: true, id_chirho: idChirho });
};

Section 8: Image Format Guidelines

Convert Generated Images to JPG

When using AI image generation:

// Convert PNG to JPG for smaller file sizes
const imageBufferChirho = await generateImageChirho(promptChirho);
const jpgBufferChirho = await convertToJpgChirho(imageBufferChirho, 85); // 85% quality

Image Requirements

Use Case Format Max Size
Photos JPG 200KB
Screenshots PNG 500KB
Icons SVG 10KB
OG Images JPG 100KB

Lazy Loading

<img
  src="/images-chirho/hero-chirho.jpg"
  alt="Description"
  loading="lazy"
  decoding="async"
/>

Section 9: Directory Structure

project-chirho/
├── CLAUDE.md                      # Points to @AGENTS.md
├── AGENTS.md                      # AI instructions (from master)
├── .env                           # Secrets (git-ignored, from ~/.env-chirho)
├── .env.example                   # Template (git-tracked)
├── wrangler.toml                  # NO SECRETS HERE
│
├── spec-chirho/                   # THE SOURCE OF TRUTH
│   ├── 00-overview-chirho.md
│   ├── 01-data-model-chirho/
│   ├── 02-features-chirho.md
│   ├── 03-routes-chirho.md
│   └── templates-chirho/
│
├── src/
│   ├── lib/
│   │   ├── server/
│   │   │   ├── db-chirho/
│   │   │   └── email-chirho/
│   │   ├── components-chirho/
│   │   │   └── FeedbackBalloonChirho.svelte
│   │   └── types-chirho/
│   └── routes/
│       ├── api-chirho/
│       │   └── feedback-chirho/
│       └── admin-fe/
│           └── feedback-chirho/
│
├── static/
│   └── images-chirho/
│
└── migrations-chirho/

Section 10: wrangler.toml (No Secrets!)

name = "project-name-chirho"
main = "src/index.ts"
compatibility_date = "2024-12-01"

# KV Namespaces
[[kv_namespaces]]
binding = "FEEDBACK_KV_CHIRHO"
id = "your-kv-id"

# D1 Database
[[d1_databases]]
binding = "DB_CHIRHO"
database_name = "project-db-chirho"
database_id = "your-d1-id"

# R2 Storage (for audit logs)
[[r2_buckets]]
binding = "AUDIT_LOGS_R2_CHIRHO"
bucket_name = "project-audit-logs-chirho"

# NEVER put secrets here!
# Use: bunx wrangler secret put KEY_NAME

Section 11: Quick Commands

# Setup
bun install                        # Install dependencies
cp ~/.env-chirho .env              # Copy secrets

# Development
bun run dev-chirho                 # Start dev server

# Secrets (production)
bunx wrangler secret put MASTER_2SMTP_API_KEY_CHIRHO
bunx wrangler secret put STRIPE_SECRET_KEY_CHIRHO
bunx wrangler secret put TURNSTILE_SECRET_KEY_CHIRHO

# Database
bunx wrangler d1 create project-db-chirho
bunx wrangler d1 migrations apply DB_CHIRHO

# Deploy
bun run deploy-chirho              # Test + deploy

Section 12: Checklist — New Project

### Setup
- [ ] CLAUDE.md points to @AGENTS.md
- [ ] AGENTS.md customized from master
- [ ] .env copied from ~/.env-chirho
- [ ] .env.example created (no values)
- [ ] .gitignore includes .env

### Secrets
- [ ] NO secrets in wrangler.toml
- [ ] Secrets documented in .env.example
- [ ] Production secrets via `wrangler secret put`

### Identifiers
- [ ] ALL variables use Chirho suffix
- [ ] ALL functions use Chirho suffix
- [ ] ALL constants use _CHIRHO suffix
- [ ] ALL routes use -chirho or -fe suffix
- [ ] ALL DB columns use _chirho suffix
- [ ] ALL lambda/error vars use Chirho suffix
- [ ] package.json scripts use -chirho suffix

### Features
- [ ] Feedback bubble on every page
- [ ] Turnstile protection on forms
- [ ] Footer with fe | loveJesus | ☧ links
- [ ] Privacy/Terms/Contact pages

### Email
- [ ] 2SMTP key configured
- [ ] SPF/DKIM/DMARC records set
- [ ] Email addresses use .fe suffix

Section 13: Summary

Key Rules

  1. SUFFIX EVERYTHING — All our identifiers, no exceptions
  2. NO SECRETS IN GIT — .env only, wrangler secret put for prod
  3. LATEST VERSIONS — Use wrangler@latest, adapter-cloudflare
  4. FEEDBACK ON EVERY PAGE — With Turnstile protection
  5. FOOTER WITH LINKS — fe | loveJesus | ☧
  6. IMAGES AS JPG — Convert generated images

Quick Reference

Category Pattern Example
TS Variables/Functions/Props camelCase + Chirho userDataChirho, fetchChirho()
Classes/Types/Interfaces PascalCase + Chirho UserChirho, ConfigChirho
Constants/Env SCREAMING_SNAKE + _CHIRHO MAX_SIZE_CHIRHO, API_KEY_CHIRHO
Directories/Files kebab-case + -chirho lib-chirho/, auth-chirho.ts
Routes kebab-case + -chirho or -fe /api-chirho/, /login-fe
DB Tables/Columns snake_case + _chirho users_chirho, created_at_chirho
CSS Classes kebab-case + -chirho .button-chirho, .hero-chirho
KV Keys kebab-case + -chirho session-chirho:id123-chirho
JSON API Props camelCase + Chirho successChirho, dataChirho
package.json scripts kebab-case + -chirho dev-chirho, build-chirho

"And whatever you do, in word or deed, do everything in the name of the Lord Jesus, giving thanks to God the Father through him." — Colossians 3:17

JESUS CHRIST IS LORD

For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life. — John 3:16

Solo Dev Testing Guide

Version: 1.0

"Trust in the LORD with all your heart, and do not lean on your own understanding." — Proverbs 3:5

But also write tests for the parts that make money.


Philosophy

You push frequently. You code on the go. You can't afford:

  • Browsers opening randomly
  • GitHub Actions burning minutes
  • 5-minute test suites blocking deploys
  • Flaky tests that fail for no reason

So we test differently.


The Strategy

What We Test

Category Example Why
Payment logic Stripe webhook handler Money
Auth functions Session validation Security
Core utilities Slugify, validation Used everywhere
API handlers POST /api/submit User-facing

What We DON'T Test Automatically

Skip in CI Why
Component rendering Use E2E for user flows instead
Every possible path Only test critical flows
Visual regression Run manually when needed
Third-party APIs Mock them

Critical Constraints

  1. HEADLESS ONLY — No GUI popping up
  2. NO CI ON EVERY PUSH — Tests run on deploy or locally
  3. VITEST FOR UNIT — Fast, terminal-based
  4. PLAYWRIGHT FOR E2E — Headless, critical flows only
  5. UNDER 30 SECONDS — Full suite must be fast
  6. MOCK EXTERNAL CALLS — Don't hit real APIs

Setup

1. Install Vitest

bun add -d vitest

2. Create vitest.config.ts

import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    include: ['src/**/*.test.ts'],
    exclude: ['**/e2e/**', '**/*.e2e.ts', '**/node_modules/**'],
    testTimeout: 5000,
    passWithNoTests: true,
  }
});

3. Package.json Scripts

{
  "scripts": {
    "test-chirho": "vitest run",
    "test-watch-chirho": "vitest",
    "test-e2e-chirho": "playwright test --headed=false",
    "test-all-chirho": "bun run test-chirho && bun run test-e2e-chirho",
    "deploy-chirho": "bun run test-chirho && bunx wrangler deploy"
  }
}

Sample Tests

Stripe Webhooks (Critical — Money)

// src/lib/server/stripe-chirho.test.ts
import { describe, it, expect, vi } from 'vitest';
import { handleWebhookChirho } from './stripe-chirho';

describe('Stripe Webhook — protects revenue', () => {
  it('updates subscription on successful payment', async () => {
    const mockDb = {
      updateSubscription: vi.fn().mockResolvedValue(true)
    };

    const event = {
      type: 'payment_intent.succeeded',
      data: { object: { customer: 'cus_123', amount: 1000 } }
    };

    const result = await handleWebhookChirho(event, mockDb);
    expect(mockDb.updateSubscription).toHaveBeenCalledWith('cus_123');
    expect(result.handled).toBe(true);
  });

  it('rejects invalid webhook signatures', async () => {
    const invalidEvent = { type: 'fake.event' };
    const result = await handleWebhookChirho(invalidEvent, {});
    expect(result.handled).toBe(false);
  });
});

Auth Functions (Critical — Security)

// src/lib/server/auth-chirho.test.ts
import { describe, it, expect } from 'vitest';
import { validateSessionChirho, hashPasswordChirho } from './auth-chirho';

describe('Auth — protects user accounts', () => {
  it('rejects expired sessions', () => {
    const expiredSession = { expiresAt: Date.now() - 1000 };
    expect(validateSessionChirho(expiredSession)).toBe(false);
  });

  it('accepts valid sessions', () => {
    const validSession = { expiresAt: Date.now() + 3600000 };
    expect(validateSessionChirho(validSession)).toBe(true);
  });

  it('produces different hashes for same password', async () => {
    const hash1 = await hashPasswordChirho('password123');
    const hash2 = await hashPasswordChirho('password123');
    expect(hash1).not.toBe(hash2); // Salted
  });
});

API Validation

// src/routes/api-chirho/submit-chirho/submit.test.ts
import { describe, it, expect } from 'vitest';
import { validateSubmissionChirho } from './validation-chirho';

describe('Submit API — protects data integrity', () => {
  it('rejects empty submissions', () => {
    const result = validateSubmissionChirho({ title: '', url: '' });
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Title required');
  });

  it('accepts valid submissions', () => {
    const result = validateSubmissionChirho({
      title: 'Test Post',
      url: 'https://example.com'
    });
    expect(result.valid).toBe(true);
  });
});

E2E Tests (Headless)

Playwright Config

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests-e2e-chirho',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,

  use: {
    headless: true,  // ALWAYS headless
    baseURL: 'http://localhost:5173',
    trace: 'on-first-retry',
  },

  webServer: {
    command: 'bun run dev',
    url: 'http://localhost:5173',
    reuseExistingServer: !process.env.CI,
  },
});

Auth Flow E2E

// tests-e2e-chirho/auth.e2e.ts
import { test, expect } from '@playwright/test';

test.describe('Auth Flow', () => {
  test('user can log in and see dashboard', async ({ page }) => {
    await page.goto('/login-fe');
    await page.fill('[name="email"]', 'test@example.com');
    await page.fill('[name="password"]', 'password123');
    await page.click('button[type="submit"]');

    await expect(page).toHaveURL('/dashboard-fe');
    await expect(page.locator('h1')).toContainText('Dashboard');
  });

  test('invalid login shows error', async ({ page }) => {
    await page.goto('/login-fe');
    await page.fill('[name="email"]', 'wrong@example.com');
    await page.fill('[name="password"]', 'wrongpass');
    await page.click('button[type="submit"]');

    await expect(page.locator('.error')).toBeVisible();
  });
});

CI Strategy

Option 1: Local Only (Simplest)

bun run deploy-chirho   # Tests run locally before deploy

Option 2: Deploy Branch (Recommended)

# .github/workflows/deploy_chirho.yaml
name: Deploy Chirho
on:
  push:
    branches: [deploy_chirho]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bun install
      - run: bun run test-chirho
      - run: bunx wrangler deploy
        if: success()
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Workflow:

  1. Push to main_chirho freely (no CI)
  2. When ready: git push github_chirho main_chirho:deploy_chirho
  3. CI tests → deploys if pass

Priority Order

When time is limited, test in this order:

  1. Stripe webhooks — Money
  2. Auth/session — Security
  3. Core API endpoints — User experience
  4. Utility functions — Foundation
  5. Everything else — Nice to have

Coverage Goals

Project Type Target
Revenue-generating 60%+ on server code
User-facing 40%+ on API handlers
Internal tools 20%+ on critical paths
Experiments 0% fine

Don't chase 100%. Test what matters.


Checklist

[ ] vitest installed
[ ] vitest.config.ts created (no browser)
[ ] test-chirho script in package.json
[ ] deploy-chirho runs tests first
[ ] Stripe webhook handler tested
[ ] Auth functions tested
[ ] Core API handlers tested
[ ] All tests pass in <30 seconds

Remember

Tests exist to give you confidence to ship, not to achieve metrics.

If a test doesn't help you sleep at night, delete it.


"Whatever you do, do it all for the glory of God." — 1 Corinthians 10:31

JESUS CHRIST IS LORD

For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life. — John 3:16

AI-Monitored Community Feedback Systems

Version: 1.0

"Listen to advice and accept instruction, that you may gain wisdom." — Proverbs 19:20

Every FaithStack project MUST implement these four AI-monitored systems.


Overview

System Purpose AI Monitors Escalates To
Feedback Balloon Per-page feedback Sentiment, bugs Support ticket
Support Tickets Help requests SLA violations Human
Feature Voting User roadmap Vote thresholds Planning
Community Q&A Peer support Unanswered AI/Ticket

1. Page-Level Feedback Balloon

Every page has a floating feedback button.

What It Captures

  • Page URL and scroll position
  • User ID or session ID
  • Feedback type: bug | suggestion | praise | confused
  • Free-text message
  • Optional screenshot

Schema

CREATE TABLE page_feedback_chirho (
  id_chirho TEXT PRIMARY KEY,
  page_url_chirho TEXT NOT NULL,
  feedback_type_chirho TEXT NOT NULL,
  message_chirho TEXT NOT NULL,
  user_id_chirho TEXT,
  session_id_chirho TEXT,
  metadata_chirho TEXT,
  screenshot_url_chirho TEXT,
  ai_sentiment_chirho TEXT,
  ai_category_chirho TEXT,
  escalated_to_chirho TEXT,
  status_chirho TEXT DEFAULT 'new',
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

KV Pattern (Fast Writes)

// Write to KV immediately
await env.FEEDBACK_KV.put(`feedback:${id}`, JSON.stringify(feedbackChirho));

// Sync to D1 via scheduled worker (every 5 min)

AI Rules

  1. Immediate escalation if contains: "broken", "can't", "error", "bug"
  2. Auto-respond to praise with thank you
  3. Alert human if >5 negative on same page in 1 hour

2. Support Ticket System

Schema

CREATE TABLE support_tickets_chirho (
  id_chirho TEXT PRIMARY KEY,
  title_chirho TEXT NOT NULL,
  description_chirho TEXT NOT NULL,
  category_chirho TEXT NOT NULL,
  priority_chirho TEXT DEFAULT 'medium',
  status_chirho TEXT DEFAULT 'open',
  user_id_chirho TEXT NOT NULL,
  user_email_chirho TEXT NOT NULL,
  assigned_to_chirho TEXT,
  sla_response_due_chirho TEXT,
  sla_resolution_due_chirho TEXT,
  first_response_at_chirho TEXT,
  resolved_at_chirho TEXT,
  ai_can_handle_chirho INTEGER DEFAULT 1,
  ai_confidence_chirho REAL,
  source_chirho TEXT,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE ticket_messages_chirho (
  id_chirho TEXT PRIMARY KEY,
  ticket_id_chirho TEXT NOT NULL,
  sender_type_chirho TEXT NOT NULL,
  sender_id_chirho TEXT,
  message_chirho TEXT NOT NULL,
  is_internal_chirho INTEGER DEFAULT 0,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

SLA Definitions

Priority First Response Resolution
Urgent 1 hour 4 hours
High 4 hours 24 hours
Medium 24 hours 72 hours
Low 48 hours 1 week

AI Rules

  1. Auto-respond to FAQ matches
  2. Escalate if confidence < 0.7
  3. Alert at 75% of SLA deadline
  4. ALWAYS escalate billing issues to human

3. Feature Voting Board

Schema

CREATE TABLE feature_requests_chirho (
  id_chirho TEXT PRIMARY KEY,
  title_chirho TEXT NOT NULL,
  description_chirho TEXT NOT NULL,
  category_chirho TEXT,
  status_chirho TEXT DEFAULT 'open',
  vote_count_chirho INTEGER DEFAULT 0,
  submitted_by_chirho TEXT,
  ai_complexity_chirho TEXT,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE feature_votes_chirho (
  id_chirho TEXT PRIMARY KEY,
  feature_id_chirho TEXT NOT NULL,
  user_id_chirho TEXT NOT NULL,
  vote_type_chirho INTEGER DEFAULT 1,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(feature_id_chirho, user_id_chirho)
);

Vote Thresholds

Votes Action
5 Add to weekly report
10 Alert human
25 High-demand flag
50 Critical need

4. Community Q&A

Schema

CREATE TABLE community_questions_chirho (
  id_chirho TEXT PRIMARY KEY,
  title_chirho TEXT NOT NULL,
  body_chirho TEXT NOT NULL,
  tags_chirho TEXT,
  user_id_chirho TEXT NOT NULL,
  status_chirho TEXT DEFAULT 'open',
  accepted_answer_id_chirho TEXT,
  ai_answer_chirho TEXT,
  escalated_to_ticket_chirho TEXT,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE community_answers_chirho (
  id_chirho TEXT PRIMARY KEY,
  question_id_chirho TEXT NOT NULL,
  user_id_chirho TEXT NOT NULL,
  body_chirho TEXT NOT NULL,
  is_accepted_chirho INTEGER DEFAULT 0,
  is_ai_generated_chirho INTEGER DEFAULT 0,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

AI Rules

  1. Auto-answer after 30 min if no human response
  2. Escalate to ticket if unanswered after 24 hours
  3. Flag inappropriate content

5. Customer Satisfaction Surveys

Types

Type Trigger Scale
CSAT After ticket resolved 1-5 stars
NPS Monthly email 0-10
CES After key action 1-7
Exit Cancellation Multiple choice

Schema

CREATE TABLE satisfaction_surveys_chirho (
  id_chirho TEXT PRIMARY KEY,
  survey_type_chirho TEXT NOT NULL,
  user_id_chirho TEXT,
  score_chirho INTEGER,
  trigger_type_chirho TEXT,
  feedback_text_chirho TEXT,
  ai_sentiment_chirho TEXT,
  ai_added_to_kb_chirho INTEGER DEFAULT 0,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

NPS Categories

Score Category Action
9-10 Promoters Request testimonial
7-8 Passives Ask improvement
0-6 Detractors Immediate follow-up

6. Internal Knowledge Base

Schema

CREATE TABLE knowledge_base_chirho (
  id_chirho TEXT PRIMARY KEY,
  title_chirho TEXT NOT NULL,
  content_chirho TEXT NOT NULL,
  category_chirho TEXT NOT NULL,
  source_type_chirho TEXT,
  ai_generated_chirho INTEGER DEFAULT 0,
  helpful_count_chirho INTEGER DEFAULT 0,
  not_helpful_count_chirho INTEGER DEFAULT 0,
  status_chirho TEXT DEFAULT 'draft',
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

Auto-Population

Create KB articles from:

  • Resolved tickets with CSAT >= 4
  • Survey feedback with insights
  • Community Q&A marked helpful

Quality Metrics

Metric Target
Coverage > 60% tickets use KB
Accuracy > 80% rated helpful
Freshness > 90% verified in 90 days
Gap Rate < 10% searches with no results

7. AI Monitoring Worker

export default {
  async scheduled(event: ScheduledEvent, env: Env) {
    // Every 5 minutes
    await processFeedbackQueueChirho(env);  // KV → D1
    await checkSlaViolationsChirho(env);
    await autoRespondQuestionsChirho(env);
    await checkFeatureThresholdsChirho(env);

    // Daily at 8am
    if (new Date().getHours() === 8) {
      await generateDailyDigestChirho(env);
    }
  }
};

8. Database-Driven Testimonials

NEVER hardcode testimonials.

Schema

CREATE TABLE testimonials_chirho (
  id_chirho TEXT PRIMARY KEY,
  author_name_chirho TEXT NOT NULL,
  author_title_chirho TEXT,
  quote_chirho TEXT NOT NULL,
  verified_chirho INTEGER DEFAULT 0,
  consent_given_chirho INTEGER DEFAULT 0,
  consent_date_chirho TEXT,
  featured_chirho INTEGER DEFAULT 0,
  active_chirho INTEGER DEFAULT 1,
  created_at_chirho TEXT DEFAULT CURRENT_TIMESTAMP
);

Pre-Launch Placeholder

<section class="testimonials-chirho">
  <h2>What Users Are Saying</h2>
  <div class="building-in-public-chirho">
    <p>We're just getting started!</p>
    <p>Be among our first users and share your experience.</p>
    <a href="/feedback-chirho">Share Your Feedback</a>
  </div>
</section>

9. Required Routes

/api-chirho/feedback-chirho       # POST: Submit feedback
/support-chirho                   # Ticket portal
/support-chirho/[id]              # Individual ticket
/features-chirho                  # Voting board
/features-chirho/[id]             # Individual feature
/community-chirho                 # Q&A board
/community-chirho/[id]            # Individual question
/admin-chirho/feedback            # Review feedback
/admin-chirho/tickets             # Manage tickets
/admin-chirho/features            # Manage features
/admin-chirho/community           # Moderate Q&A
/admin-chirho/testimonials        # Approve testimonials
/admin-chirho/surveys             # View surveys
/admin-chirho/kb-chirho           # Knowledge base

10. Implementation Checklist

### Feedback Balloon
- [ ] FeedbackBalloonChirho.svelte component
- [ ] /api-chirho/feedback-chirho endpoint
- [ ] page_feedback_chirho table
- [ ] FEEDBACK_KV namespace
- [ ] AI sentiment analysis

### Support Tickets
- [ ] /support-chirho routes
- [ ] support_tickets_chirho table
- [ ] ticket_messages_chirho table
- [ ] SLA monitoring
- [ ] Email notifications

### Feature Voting
- [ ] /features-chirho routes
- [ ] feature_requests_chirho table
- [ ] feature_votes_chirho table
- [ ] Vote threshold alerts

### Community Q&A
- [ ] /community-chirho routes
- [ ] community_questions_chirho table
- [ ] community_answers_chirho table
- [ ] AI auto-answer after 30 min

### Surveys & KB
- [ ] satisfaction_surveys_chirho table
- [ ] knowledge_base_chirho table
- [ ] Auto-population rules
- [ ] Helpful feedback loop

### AI Monitoring
- [ ] Scheduled worker (5 min)
- [ ] KV → D1 sync
- [ ] SLA checks
- [ ] Daily digest at 8am

### Testimonials
- [ ] testimonials_chirho table
- [ ] No hardcoded testimonials
- [ ] Consent tracking
- [ ] Admin approval

11. AI Agent Prompt

Copy this into your project's AGENTS.md:

## AI Community Monitoring Instructions

As the AI agent, you monitor and respond to all community feedback.

### Responsibilities

1. **Feedback Monitoring**
   - Review all incoming feedback
   - Analyze sentiment
   - Auto-respond to praise
   - Escalate bugs to tickets

2. **Support Ticket Handling**
   - Auto-respond to FAQ matches
   - Escalate if confidence < 0.7
   - ALWAYS escalate billing to human
   - Alert at 75% SLA deadline

3. **Feature Requests**
   - Detect duplicates
   - Estimate complexity
   - Alert at 10+ votes
   - Notify voters when shipped

4. **Community Q&A**
   - AI answer after 30 min
   - Escalate after 24 hours
   - Flag inappropriate content

5. **Surveys**
   - Analyze sentiment
   - Extract insights for KB
   - Follow up with detractors

6. **Knowledge Base**
   - Query before responding
   - Create from resolved tickets
   - Track helpful ratings

7. **Daily Digest (8am)**
   - Feedback count + sentiment
   - Open tickets + SLA status
   - Top voted features
   - Unanswered questions

"And whatever you do, in word or deed, do everything in the name of the Lord Jesus." — Colossians 3:17

JESUS CHRIST IS LORD

For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life. — John 3:16

Database Audit Pipeline

Version: 1.0

Every database mutation is logged to a PRIVATE R2 bucket with full user context.


Setup

1. Create R2 Bucket

wrangler r2 bucket create your-project-audit-logs-chirho

2. Add to wrangler.toml

[[r2_buckets]]
binding = "AUDIT_LOGS_R2"
bucket_name = "your-project-audit-logs-chirho"

Types

interface AuditContextChirho {
  userId?: string;
  sessionId?: string;
  userEmail?: string;
  requestId?: string;
  ipAddress?: string;
  userAgent?: string;
  cfRay?: string;
  country?: string;
  path?: string;
  method?: string;
}

interface AuditEntryChirho {
  id: string;
  timestamp: string;
  project: string;
  operation: 'INSERT' | 'UPDATE' | 'DELETE' | 'BATCH';
  table: string;
  primaryKey?: string | Record<string, unknown>;
  oldValues?: Record<string, unknown>;
  newValues?: Record<string, unknown>;
  affectedRows?: number;
  context: AuditContextChirho;
  queryHash?: string;
  executionTimeMs?: number;
}

interface AuditBatchChirho {
  batchId: string;
  project: string;
  startTime: string;
  endTime: string;
  entries: AuditEntryChirho[];
  entryCount: number;
}

Audit Logger Class

export class DatabaseAuditLoggerChirho {
  private projectChirho: string;
  private r2Chirho: R2Bucket;
  private batchChirho: AuditEntryChirho[] = [];
  private batchSizeChirho = 50;
  private flushIntervalMsChirho = 5000;
  private lastFlushChirho = Date.now();

  constructor(projectChirho: string, r2Chirho: R2Bucket) {
    this.projectChirho = projectChirho;
    this.r2Chirho = r2Chirho;
  }

  static extractContextChirho(
    request: Request,
    extras?: Partial<AuditContextChirho>
  ): AuditContextChirho {
    const cfChirho = (request as any).cf || {};
    return {
      requestId: request.headers.get('cf-ray') || crypto.randomUUID(),
      ipAddress: request.headers.get('cf-connecting-ip') || 'unknown',
      userAgent: request.headers.get('user-agent')?.substring(0, 200),
      cfRay: request.headers.get('cf-ray'),
      country: cfChirho.country,
      path: new URL(request.url).pathname,
      method: request.method,
      ...extras
    };
  }

  async logInsertChirho(
    table: string,
    newValues: Record<string, unknown>,
    primaryKey: string | Record<string, unknown>,
    context: AuditContextChirho
  ): Promise<void> {
    await this.addEntryChirho({
      id: crypto.randomUUID(),
      timestamp: new Date().toISOString(),
      project: this.projectChirho,
      operation: 'INSERT',
      table,
      primaryKey,
      newValues: this.sanitizeValuesChirho(newValues),
      context
    });
  }

  async logUpdateChirho(
    table: string,
    oldValues: Record<string, unknown> | undefined,
    newValues: Record<string, unknown>,
    primaryKey: string | Record<string, unknown>,
    context: AuditContextChirho
  ): Promise<void> {
    await this.addEntryChirho({
      id: crypto.randomUUID(),
      timestamp: new Date().toISOString(),
      project: this.projectChirho,
      operation: 'UPDATE',
      table,
      primaryKey,
      oldValues: oldValues ? this.sanitizeValuesChirho(oldValues) : undefined,
      newValues: this.sanitizeValuesChirho(newValues),
      context
    });
  }

  async logDeleteChirho(
    table: string,
    oldValues: Record<string, unknown> | undefined,
    primaryKey: string | Record<string, unknown>,
    context: AuditContextChirho
  ): Promise<void> {
    await this.addEntryChirho({
      id: crypto.randomUUID(),
      timestamp: new Date().toISOString(),
      project: this.projectChirho,
      operation: 'DELETE',
      table,
      primaryKey,
      oldValues: oldValues ? this.sanitizeValuesChirho(oldValues) : undefined,
      context
    });
  }

  private sanitizeValuesChirho(
    values: Record<string, unknown>
  ): Record<string, unknown> {
    const sensitiveFieldsChirho = [
      'password', 'password_hash', 'token', 'api_key', 'secret',
      'credit_card', 'ssn', 'social_security', 'private_key',
      'session_token', 'refresh_token', 'access_token'
    ];

    const sanitizedChirho: Record<string, unknown> = {};

    for (const [key, value] of Object.entries(values)) {
      const keyLowerChirho = key.toLowerCase();

      if (sensitiveFieldsChirho.some(f => keyLowerChirho.includes(f))) {
        sanitizedChirho[key] = '[REDACTED]';
      } else if (typeof value === 'string' && value.length > 1000) {
        sanitizedChirho[key] = value.substring(0, 1000) + '...[TRUNCATED]';
      } else {
        sanitizedChirho[key] = value;
      }
    }

    return sanitizedChirho;
  }

  private async addEntryChirho(entry: AuditEntryChirho): Promise<void> {
    this.batchChirho.push(entry);

    const shouldFlushChirho =
      this.batchChirho.length >= this.batchSizeChirho ||
      Date.now() - this.lastFlushChirho >= this.flushIntervalMsChirho;

    if (shouldFlushChirho) {
      await this.flushChirho();
    }
  }

  async flushChirho(): Promise<void> {
    if (this.batchChirho.length === 0) return;

    const nowChirho = new Date();
    const batchChirho: AuditBatchChirho = {
      batchId: crypto.randomUUID(),
      project: this.projectChirho,
      startTime: this.batchChirho[0].timestamp,
      endTime: nowChirho.toISOString(),
      entries: this.batchChirho,
      entryCount: this.batchChirho.length
    };

    // R2 key: /{project}/{year}/{month}/{day}/{hour}/{batch-id}.json
    const keyChirho = [
      this.projectChirho,
      nowChirho.getUTCFullYear(),
      String(nowChirho.getUTCMonth() + 1).padStart(2, '0'),
      String(nowChirho.getUTCDate()).padStart(2, '0'),
      String(nowChirho.getUTCHours()).padStart(2, '0'),
      `${batchChirho.batchId}.json`
    ].join('/');

    await this.r2Chirho.put(keyChirho, JSON.stringify(batchChirho, null, 2), {
      httpMetadata: { contentType: 'application/json' },
      customMetadata: {
        project: this.projectChirho,
        entryCount: String(batchChirho.entryCount),
        startTime: batchChirho.startTime,
        endTime: batchChirho.endTime
      }
    });

    this.batchChirho = [];
    this.lastFlushChirho = Date.now();
  }
}

Audited D1 Wrapper

export class AuditedD1Chirho {
  private dbChirho: D1Database;
  private auditChirho: DatabaseAuditLoggerChirho;
  private contextChirho: AuditContextChirho;

  constructor(
    db: D1Database,
    audit: DatabaseAuditLoggerChirho,
    context: AuditContextChirho
  ) {
    this.dbChirho = db;
    this.auditChirho = audit;
    this.contextChirho = context;
  }

  prepare(query: string): D1PreparedStatement {
    return this.dbChirho.prepare(query);
  }

  async batch<T = unknown>(
    statements: D1PreparedStatement[]
  ): Promise<D1Result<T>[]> {
    return this.dbChirho.batch(statements);
  }

  async insertChirho<T extends Record<string, unknown>>(
    table: string,
    values: T,
    primaryKeyField = 'id_chirho'
  ): Promise<D1Result> {
    const columnsChirho = Object.keys(values);
    const placeholdersChirho = columnsChirho.map(() => '?').join(', ');
    const queryChirho = `INSERT INTO ${table} (${columnsChirho.join(', ')}) VALUES (${placeholdersChirho})`;

    const resultChirho = await this.dbChirho
      .prepare(queryChirho)
      .bind(...Object.values(values))
      .run();

    await this.auditChirho.logInsertChirho(
      table,
      values,
      values[primaryKeyField] as string || 'unknown',
      this.contextChirho
    );

    return resultChirho;
  }

  async updateChirho<T extends Record<string, unknown>>(
    table: string,
    values: T,
    whereClause: string,
    whereParams: unknown[],
    primaryKeyField = 'id_chirho'
  ): Promise<D1Result> {
    // Get old values first
    const selectQueryChirho = `SELECT * FROM ${table} WHERE ${whereClause}`;
    const oldRowsChirho = await this.dbChirho
      .prepare(selectQueryChirho)
      .bind(...whereParams)
      .all();

    // Execute update
    const setClauseChirho = Object.keys(values).map(k => `${k} = ?`).join(', ');
    const updateQueryChirho = `UPDATE ${table} SET ${setClauseChirho} WHERE ${whereClause}`;

    const resultChirho = await this.dbChirho
      .prepare(updateQueryChirho)
      .bind(...Object.values(values), ...whereParams)
      .run();

    // Log each updated row
    for (const oldRowChirho of oldRowsChirho.results || []) {
      await this.auditChirho.logUpdateChirho(
        table,
        oldRowChirho as Record<string, unknown>,
        values,
        (oldRowChirho as Record<string, unknown>)[primaryKeyField] as string,
        this.contextChirho
      );
    }

    return resultChirho;
  }

  async deleteChirho(
    table: string,
    whereClause: string,
    whereParams: unknown[],
    primaryKeyField = 'id_chirho'
  ): Promise<D1Result> {
    // Get values before delete
    const selectQueryChirho = `SELECT * FROM ${table} WHERE ${whereClause}`;
    const rowsChirho = await this.dbChirho
      .prepare(selectQueryChirho)
      .bind(...whereParams)
      .all();

    // Execute delete
    const deleteQueryChirho = `DELETE FROM ${table} WHERE ${whereClause}`;
    const resultChirho = await this.dbChirho
      .prepare(deleteQueryChirho)
      .bind(...whereParams)
      .run();

    // Log each deleted row
    for (const rowChirho of rowsChirho.results || []) {
      await this.auditChirho.logDeleteChirho(
        table,
        rowChirho as Record<string, unknown>,
        (rowChirho as Record<string, unknown>)[primaryKeyField] as string,
        this.contextChirho
      );
    }

    return resultChirho;
  }

  async queryChirho<T = unknown>(
    query: string,
    params: unknown[] = []
  ): Promise<D1Result<T>> {
    return this.dbChirho.prepare(query).bind(...params).all();
  }

  async flushAuditChirho(): Promise<void> {
    await this.auditChirho.flushChirho();
  }
}

Factory Function

export interface AuditedDbEnvChirho {
  DB: D1Database;
  AUDIT_LOGS_R2: R2Bucket;
}

export function createAuditedDbChirho(
  env: AuditedDbEnvChirho,
  request: Request,
  projectName: string,
  userContext?: { userId?: string; sessionId?: string; userEmail?: string }
): AuditedD1Chirho {
  const auditLoggerChirho = new DatabaseAuditLoggerChirho(
    projectName,
    env.AUDIT_LOGS_R2
  );
  const contextChirho = DatabaseAuditLoggerChirho.extractContextChirho(
    request,
    userContext
  );

  return new AuditedD1Chirho(env.DB, auditLoggerChirho, contextChirho);
}

Usage Example

import { createAuditedDbChirho } from './db-audit-pipeline-chirho';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Get user from session
    const userChirho = await getUserFromSession(request, env);

    // Create audited database
    const dbChirho = createAuditedDbChirho(env, request, 'my-project-chirho', {
      userId: userChirho?.id,
      sessionId: userChirho?.sessionId,
      userEmail: userChirho?.email
    });

    // All mutations are automatically logged!
    await dbChirho.insertChirho('users_chirho', {
      id_chirho: crypto.randomUUID(),
      email_chirho: 'new@user.com',
      name_chirho: 'New User'
    });

    await dbChirho.updateChirho(
      'users_chirho',
      { name_chirho: 'Updated Name' },
      'id_chirho = ?',
      ['user-123']
    );

    await dbChirho.deleteChirho(
      'sessions_chirho',
      'expires_at_chirho < ?',
      [Date.now()]
    );

    // IMPORTANT: Flush at end of request
    await dbChirho.flushAuditChirho();

    return new Response('OK');
  }
};

R2 Storage Structure

/{project}/{year}/{month}/{day}/{hour}/{batch-id}.json

Example:
/manna-chirho/2025/12/29/14/abc123-def456.json

Each batch file contains:

  • Batch ID and project name
  • Start/end timestamps
  • Array of audit entries
  • Entry count

Sensitive Data Handling

Automatically redacted fields:

  • password, password_hash
  • token, api_key, secret
  • credit_card, ssn, social_security
  • private_key, session_token
  • refresh_token, access_token

Long strings (>1000 chars) are truncated.


Implementation Checklist

### Database Audit Pipeline
- [ ] R2 bucket created (private)
- [ ] AUDIT_LOGS_R2 binding in wrangler.toml
- [ ] DatabaseAuditLoggerChirho class
- [ ] AuditedD1Chirho wrapper
- [ ] All mutations using wrapper
- [ ] flushAuditChirho() at request end
- [ ] Sensitive fields list complete
- [ ] /admin-chirho/audit view

JESUS CHRIST IS LORD

For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life. — John 3:16

Project Excellence & Best Practices

Version: 1.5

"Whatever you do, work at it with all your heart, as working for the Lord." — Colossians 3:23


1. Required Legal Documents

Every public-facing project MUST have:

Document Route Required For
Privacy Policy /privacy-fe All projects
Terms of Service /terms-fe All projects
Cookie Policy In privacy or /cookies-fe EU users
Refund Policy /refunds-fe Paid products
Accessibility /accessibility-fe Recommended

Footer Structure

<footer>
  <nav>
    <a href="/privacy-fe">Privacy Policy</a>
    <a href="/terms-fe">Terms of Service</a>
    <a href="/refunds-fe">Refund Policy</a>
    <a href="/contact-fe">Contact</a>
  </nav>
  <p>&copy; 2025 Your Company. All rights reserved.</p>
</footer>

2. User Feedback System

Database Schema

CREATE TABLE feedback_chirho (
  id_chirho TEXT PRIMARY KEY,
  type_chirho TEXT CHECK(type_chirho IN ('bug', 'feature', 'general')),
  subject_chirho TEXT NOT NULL,
  message_chirho TEXT NOT NULL,
  email_chirho TEXT,
  rating_chirho INTEGER CHECK(rating_chirho >= 1 AND rating_chirho <= 5),
  user_id_chirho TEXT,
  page_url_chirho TEXT,
  public_chirho INTEGER DEFAULT 0,
  created_at_chirho INTEGER NOT NULL
);

Route

Route Method Purpose
/api-fe/feedback-fe POST Submit feedback

3. Feature Suggestion System

Routes

Route Purpose
/features-fe List suggestions
/features-fe/new-fe Submit suggestion
/features-fe/:id View with comments

Schema

CREATE TABLE feature_suggestions_chirho (
  id_chirho TEXT PRIMARY KEY,
  title_chirho TEXT NOT NULL,
  description_chirho TEXT,
  status_chirho TEXT DEFAULT 'pending',
  user_id_chirho TEXT NOT NULL,
  upvotes_chirho INTEGER DEFAULT 0,
  created_at_chirho INTEGER NOT NULL
);

CREATE TABLE feature_votes_chirho (
  suggestion_id_chirho TEXT NOT NULL,
  user_id_chirho TEXT NOT NULL,
  vote_chirho INTEGER CHECK(vote_chirho IN (-1, 1)),
  PRIMARY KEY (suggestion_id_chirho, user_id_chirho)
);

4. API Key Management

Key Format

prefix_base64randomdata

Examples:
sk_live_7fK9xMq2nP4wL8vR3tY6...  (live secret)
sk_test_...                      (test secret)
pk_live_...                      (publishable)

Schema

CREATE TABLE api_keys_chirho (
  id_chirho TEXT PRIMARY KEY,
  user_id_chirho TEXT NOT NULL,
  key_hash_chirho TEXT NOT NULL,
  key_prefix_chirho TEXT NOT NULL,
  name_chirho TEXT,
  scopes_chirho TEXT,
  last_used_at_chirho INTEGER,
  expires_at_chirho INTEGER,
  revoked_at_chirho INTEGER,
  created_at_chirho INTEGER NOT NULL
);

Hashing (SHA-256 x4)

async function hashApiKeyChirho(keyChirho: string): Promise<string> {
  let hashChirho = keyChirho;
  for (let i = 0; i < 4; i++) {
    const dataChirho = new TextEncoder().encode(hashChirho);
    const bufferChirho = await crypto.subtle.digest('SHA-256', dataChirho);
    hashChirho = Array.from(new Uint8Array(bufferChirho))
      .map(b => b.toString(16).padStart(2, '0')).join('');
  }
  return hashChirho;
}

5. Security Requirements

Password Hashing

import bcrypt from 'bcryptjs';

// Hash with 12 rounds (Cloudflare Workers compatible)
const hashChirho = await bcrypt.hash(passwordChirho, 12);
const validChirho = await bcrypt.compare(passwordChirho, hashChirho);

Session Management

  • Use secure, HTTP-only cookies
  • Implement CSRF protection
  • Set SameSite=Strict or Lax
  • Appropriate expiration

Security Headers

const securityHeadersChirho = {
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY',
  'X-XSS-Protection': '1; mode=block',
  'Referrer-Policy': 'strict-origin-when-cross-origin'
};

6. Admin Routes

Route Purpose
/admin-fe Dashboard overview
/admin-fe/users-fe User management
/admin-fe/content-fe Content moderation
/admin-fe/settings-fe System config
/admin-fe/logs-fe Activity logs

Access Control

const ADMIN_ROLES_CHIRHO = ['admin', 'super_admin', 'moderator'];

async function requireAdminChirho(requestChirho: Request) {
  const sessionChirho = await getSessionChirho(requestChirho);
  if (!sessionChirho || !ADMIN_ROLES_CHIRHO.includes(sessionChirho.roleChirho)) {
    throw redirect(302, '/login-fe?error=unauthorized');
  }
}

7. Email System (2SMTP)

Setup

// Environment variables
REMAIL_API_KEY_CHIRHO=rk_your_key
REMAIL_ENDPOINT_CHIRHO=https://2smtp.com

Sending Email

const responseChirho = await fetch('https://2smtp.com/api_fe/send_email_fe', {
  method: 'POST',
  headers: {
    'X-API-Key': env.REMAIL_API_KEY_CHIRHO,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    to_chirho: ['user@example.com'],
    subject_chirho: 'Welcome!',
    body_html_chirho: '<p>Hello World</p>'
  })
});

Pricing

  • 10,000 emails for $5 (never expires)
  • Rate limit: 100/minute per key
  • Max recipients: 1000 per request

8. GDPR Compliance

Requirements

  1. Consent Banner — Cookie consent before non-essential cookies
  2. Data Export — Users can download their data (Article 20)
  3. Data Deletion — Users can request deletion (Article 17)
  4. Privacy Policy — Clear explanation of processing
  5. Lawful Basis — Document legal basis

Data Export Endpoint

GET /api-fe/export-my-data-fe
Authorization: Bearer <token>

Returns: ZIP file with user data in JSON

Data Deletion Endpoint

DELETE /api-fe/delete-my-account-fe
Authorization: Bearer <token>

Deletes: All user data within 30 days

9. AI Support Integration

Voice Support (ElevenLabs)

  • Use ElevenLabs Conversational AI
  • Integrate via webhook for real-time data
  • Clear knowledge base
  • Human escalation triggers
  • Log conversations

Webhook Integration

// /api-fe/elevenlabs-webhook-fe
export async function POST({ request }) {
  const dataChirho = await request.json();

  if (dataChirho.action === 'lookup_order') {
    const orderChirho = await getOrderChirho(dataChirho.orderId);
    return json({ orderChirho });
  }

  return json({ success: true });
}

10. Referral System

Payout Delay

IMPORTANT: 2-week minimum delay before referral payouts.

Reasons:

  • Prevents chargebacks from being paid
  • Prevents refund abuse
  • Prevents fake account fraud
  • Time to receive money

Schema

CREATE TABLE referral_payouts_chirho (
  id_chirho TEXT PRIMARY KEY,
  referrer_id_chirho TEXT NOT NULL,
  referred_id_chirho TEXT NOT NULL,
  amount_cents_chirho INTEGER NOT NULL,
  eligible_at_chirho INTEGER NOT NULL,
  paid_at_chirho INTEGER,
  status_chirho TEXT DEFAULT 'pending',
  created_at_chirho INTEGER NOT NULL
);

11. Stripe Webhooks on Cloudflare Workers

CRITICAL: Use Async Signature Verification

Cloudflare Workers use SubtleCrypto which is async-only. The synchronous constructEvent() will fail.

❌ Error: SubtleCryptoProvider cannot be used in a synchronous context.
   Use `await constructEventAsync(...)` instead of `constructEvent(...)`

Correct Implementation

// src/routes/api-chirho/stripe-webhook-chirho/+server.ts
import Stripe from 'stripe';
import { error, json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

const stripeChirho = new Stripe(env.STRIPE_SECRET_KEY_CHIRHO);

export const POST: RequestHandler = async ({ request, platform }) => {
  const payloadChirho = await request.text();
  const signatureChirho = request.headers.get('stripe-signature');

  if (!signatureChirho) {
    throw error(400, 'Missing Stripe signature');
  }

  let eventChirho: Stripe.Event;

  try {
    // ✅ CORRECT: Use constructEventAsync for Workers
    eventChirho = await stripeChirho.webhooks.constructEventAsync(
      payloadChirho,
      signatureChirho,
      platform.env.STRIPE_WEBHOOK_SECRET_CHIRHO
    );
  } catch (errChirho) {
    console.error('Webhook signature verification failed:', errChirho);
    throw error(400, 'Invalid signature');
  }

  // Handle the event
  switch (eventChirho.type) {
    case 'checkout.session.completed':
      const sessionChirho = eventChirho.data.object as Stripe.Checkout.Session;
      await handleCheckoutCompleteChirho(sessionChirho, platform.env);
      break;

    case 'customer.subscription.updated':
      const subscriptionChirho = eventChirho.data.object as Stripe.Subscription;
      await handleSubscriptionUpdateChirho(subscriptionChirho, platform.env);
      break;

    case 'customer.subscription.deleted':
      const cancelledChirho = eventChirho.data.object as Stripe.Subscription;
      await handleSubscriptionCancelChirho(cancelledChirho, platform.env);
      break;

    case 'invoice.payment_failed':
      const invoiceChirho = eventChirho.data.object as Stripe.Invoice;
      await handlePaymentFailedChirho(invoiceChirho, platform.env);
      break;

    default:
      console.log(`Unhandled event type: ${eventChirho.type}`);
  }

  return json({ receivedChirho: true });
};

Common Gotcha

// ❌ WRONG - This fails on Workers
const eventChirho = stripeChirho.webhooks.constructEvent(
  payloadChirho,
  signatureChirho,
  webhookSecretChirho
);

// ✅ CORRECT - Use async version
const eventChirho = await stripeChirho.webhooks.constructEventAsync(
  payloadChirho,
  signatureChirho,
  webhookSecretChirho
);

12. Stripe Coupons & Promotions

Concepts

Concept Description
Coupon The discount (20% off, $10 off)
Promotion Code Customer-facing code (WELCOME20)

Apply at Checkout

const sessionChirho = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: priceIdChirho, quantity: 1 }],
  success_url: `${originChirho}/success-fe`,
  cancel_url: `${originChirho}/pricing-fe`,
  // Allow customers to enter codes
  allow_promotion_codes: true,
});

13. Cloudflare Workers Project Setup

Use wrangler deploy (NOT wrangler pages deploy)

# ✅ CORRECT - Deploys to Workers
bunx wrangler deploy

# ❌ WRONG - This is for Pages projects
bunx wrangler pages deploy

Install adapter-cloudflare

bun add -d @sveltejs/adapter-cloudflare

svelte.config.js

import adapterChirho from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const configChirho = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapterChirho({
      routes: {
        include: ['/*'],
        exclude: ['<all>']
      },
      platformProxy: {
        configPath: 'wrangler.toml',
        persist: { path: '.wrangler/state/v3' }
      }
    })
  }
};

export default configChirho;

package.json Scripts

{
  "scripts": {
    "dev-chirho": "vite dev",
    "build-chirho": "vite build",
    "preview-chirho": "vite preview",
    "test-chirho": "vitest run",
    "test-e2e-chirho": "playwright test",
    "check-chirho": "svelte-kit sync && svelte-check",
    "deploy-chirho": "bun run scripts-chirho/deploy-chirho.ts"
  }
}

Deploy Script (scripts-chirho/deploy-chirho.ts)

#!/usr/bin/env bun
/**
 * deploy-chirho.ts
 *
 * Deployment script that:
 * 1. Runs tests locally
 * 2. Builds the project
 * 3. Pushes to deploy-chirho branch (triggers CI)
 * 4. Optionally deploys directly
 */

import { $ } from 'bun';

const DEPLOY_BRANCH_CHIRHO = 'deploy-chirho';

async function deployChirho() {
  console.log('🚀 Starting deployment...\n');

  // 1. Run tests
  console.log('📋 Running tests...');
  const testResultChirho = await $`bun run test-chirho`.quiet().nothrow();
  if (testResultChirho.exitCode !== 0) {
    console.error('❌ Tests failed. Aborting deployment.');
    console.error(testResultChirho.stderr.toString());
    process.exit(1);
  }
  console.log('✅ Tests passed!\n');

  // 2. Run type check
  console.log('🔍 Running type check...');
  const checkResultChirho = await $`bun run check-chirho`.quiet().nothrow();
  if (checkResultChirho.exitCode !== 0) {
    console.error('❌ Type check failed. Aborting deployment.');
    console.error(checkResultChirho.stderr.toString());
    process.exit(1);
  }
  console.log('✅ Type check passed!\n');

  // 3. Build
  console.log('🔨 Building project...');
  const buildResultChirho = await $`bun run build-chirho`.quiet().nothrow();
  if (buildResultChirho.exitCode !== 0) {
    console.error('❌ Build failed. Aborting deployment.');
    console.error(buildResultChirho.stderr.toString());
    process.exit(1);
  }
  console.log('✅ Build successful!\n');

  // 4. Check for uncommitted changes
  const statusChirho = await $`git status --porcelain`.text();
  if (statusChirho.trim()) {
    console.log('📝 Committing changes...');
    await $`git add -A`;
    await $`git commit -m "chore: pre-deploy build [AI-CHIRHO]"`.nothrow();
  }

  // 5. Push to deploy branch
  console.log(`🌿 Pushing to ${DEPLOY_BRANCH_CHIRHO} branch...`);
  const currentBranchChirho = (await $`git branch --show-current`.text()).trim();
  await $`git push origin ${currentBranchChirho}:${DEPLOY_BRANCH_CHIRHO} --force`;
  console.log('✅ Pushed to deploy branch!\n');

  // 6. Deploy to Cloudflare Workers
  console.log('☁️ Deploying to Cloudflare Workers...');
  const deployResultChirho = await $`bunx wrangler deploy`.nothrow();
  if (deployResultChirho.exitCode !== 0) {
    console.error('❌ Deployment failed.');
    process.exit(1);
  }

  console.log('\n🎉 Deployment complete!');
  console.log('JESUS CHRIST IS LORD');
}

deployChirho().catch((errChirho) => {
  console.error('Deployment error:', errChirho);
  process.exit(1);
});

CI Workflow (.github/workflows/deploy-chirho.yaml)

name: Deploy Chirho
on:
  push:
    branches: [deploy-chirho]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Install dependencies
        run: bun install

      - name: Run tests
        run: bun run test-chirho

      - name: Build
        run: bun run build-chirho

      - name: Deploy to Cloudflare
        run: bunx wrangler deploy
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

wrangler.toml - Static Assets Configuration

CRITICAL: If static assets are not uploading, add this configuration:

name = "project-name-chirho"
main = ".svelte-kit/cloudflare/_worker.js"
compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"]

# Required for static assets to upload correctly
[assets]
directory = ".svelte-kit/cloudflare"
binding = "ASSETS"

Troubleshooting Static Assets

If you see: No static assets to upload or assets not loading:

  1. Check build output:

    bun run build-chirho
    ls -la .svelte-kit/cloudflare/
  2. Verify assets directory exists:

    # Should contain _app/, favicon.png, etc.
    ls .svelte-kit/cloudflare/_app/
  3. Force asset upload with explicit config:

    # wrangler.toml
    [assets]
    directory = ".svelte-kit/cloudflare"
  4. Deploy with verbose output:

    bunx wrangler deploy --verbose

Common Deployment Issues

Issue Solution
"No static assets to upload" Add [assets] section to wrangler.toml
Assets 404 after deploy Verify directory points to build output
Worker not found Check main path matches build output
KV/D1 not available Add bindings to wrangler.toml
Using wrong deploy command Use wrangler deploy, NOT wrangler pages deploy

Complete wrangler.toml Example

name = "my-project-chirho"
main = ".svelte-kit/cloudflare/_worker.js"
compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"]

[assets]
directory = ".svelte-kit/cloudflare"
binding = "ASSETS"

[[kv_namespaces]]
binding = "SESSIONS_KV_CHIRHO"
id = "your-kv-id"

[[d1_databases]]
binding = "DB_CHIRHO"
database_name = "my-project-db-chirho"
database_id = "your-d1-id"

[[r2_buckets]]
binding = "UPLOADS_R2_CHIRHO"
bucket_name = "my-project-uploads-chirho"

# NEVER put secrets here - use: bunx wrangler secret put SECRET_NAME

14. Rich Previews (Open Graph)

Required Meta Tags

<meta property="og:title" content="Page Title">
<meta property="og:description" content="Description">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Page Title">
<meta name="twitter:description" content="Description">
<meta name="twitter:image" content="https://example.com/image.jpg">

Image Requirements

  • Minimum: 1200x630px
  • Max file size: 5MB
  • Formats: PNG, JPG, GIF

15. Analytics (Privacy-Focused)

Recommended: Plausible or Umami

<script defer data-domain="yoursite.com"
        src="https://plausible.io/js/script.js"></script>

Benefits:

  • No cookies required
  • GDPR compliant by default
  • Lightweight (<1KB)
  • No personal data collected

16. Error Handling

User-Facing Errors

// lib/errors-chirho.ts
export class UserErrorChirho extends Error {
  constructor(
    message: string,
    public codeChirho: string,
    public statusChirho: number = 400
  ) {
    super(message);
  }
}

// Usage
throw new UserErrorChirho(
  'Email already registered',
  'EMAIL_EXISTS',
  409
);

Error Page

<!-- /error-fe/+page.svelte -->
<script>
  export let data;
</script>

<div class="error-container-chirho">
  <h1>{data.status}</h1>
  <p>{data.message}</p>
  <a href="/">Go Home</a>
</div>

17. Performance

Targets

Metric Target
LCP < 2.5s
FID < 100ms
CLS < 0.1
TTFB < 600ms

Optimizations

  1. Images — Use WebP, lazy loading
  2. Fonts — System fonts or font-display: swap
  3. JS — Code splitting, defer non-critical
  4. CSS — Inline critical, async the rest
  5. Caching — Proper Cache-Control headers

18. Accessibility (WCAG 2.1 AA)

Requirements

  • Keyboard navigation for all interactions
  • Color contrast ratio ≥ 4.5:1
  • Alt text for images
  • ARIA labels for interactive elements
  • Focus indicators visible
  • Skip navigation link

Testing

# Lighthouse accessibility audit
npx lighthouse https://yoursite.com --only-categories=accessibility

19. Mobile Responsiveness

Breakpoints

/* Mobile first */
@media (min-width: 640px) { /* sm */ }
@media (min-width: 768px) { /* md */ }
@media (min-width: 1024px) { /* lg */ }
@media (min-width: 1280px) { /* xl */ }

Touch Targets

  • Minimum 44x44px for buttons
  • Adequate spacing between targets
  • Test on actual devices

20. Internationalization (i18n)

If supporting multiple languages:

Setup

// lib/i18n-chirho.ts
const translationsChirho = {
  en: { welcome: 'Welcome' },
  es: { welcome: 'Bienvenido' }
};

export function t(key: string, locale = 'en'): string {
  return translationsChirho[locale]?.[key] || key;
}

Date/Number Formatting

new Intl.DateTimeFormat(locale).format(date);
new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);

21. Rate Limiting

Implementation

// Using Cloudflare's Rate Limiting
const rateLimitChirho = {
  requests: 100,
  period: 60, // seconds
  key: 'ip' // or 'user_id'
};

// Or manual with KV
async function checkRateLimitChirho(
  env: Env,
  keyChirho: string,
  limitChirho: number,
  windowChirho: number
): Promise<boolean> {
  const countChirho = await env.RATE_LIMIT_KV.get(keyChirho);
  if (countChirho && parseInt(countChirho) >= limitChirho) {
    return false; // Rate limited
  }
  await env.RATE_LIMIT_KV.put(
    keyChirho,
    String((parseInt(countChirho || '0') + 1)),
    { expirationTtl: windowChirho }
  );
  return true;
}

22. Monitoring & Logging

Error Tracking

// Catch and log errors
try {
  await doSomethingChirho();
} catch (errorChirho) {
  console.error('[ERROR]', {
    message: errorChirho.message,
    stack: errorChirho.stack,
    timestamp: new Date().toISOString(),
    path: request.url,
    user: userIdChirho
  });
  throw errorChirho;
}

Health Check Endpoint

// /api-fe/health-fe
export async function GET({ env }) {
  const checksChirho = {
    database: await checkDbChirho(env),
    kv: await checkKvChirho(env),
    timestamp: new Date().toISOString()
  };

  const healthyChirho = Object.values(checksChirho)
    .every(c => c === true || typeof c === 'string');

  return json(checksChirho, {
    status: healthyChirho ? 200 : 503
  });
}

Implementation Checklist

### Legal & Compliance
- [ ] Privacy policy at /privacy-fe
- [ ] Terms of service at /terms-fe
- [ ] Cookie consent banner (EU)
- [ ] Data export endpoint
- [ ] Data deletion endpoint

### Security
- [ ] bcrypt for passwords (12 rounds)
- [ ] Security headers set
- [ ] HTTPS everywhere
- [ ] Rate limiting
- [ ] Input validation

### Stripe Integration
- [ ] Use constructEventAsync (NOT constructEvent)
- [ ] Webhook signature verification
- [ ] Handle checkout.session.completed
- [ ] Handle subscription updates
- [ ] Handle payment failures

### Cloudflare Workers Setup
- [ ] Use adapter-cloudflare (NOT adapter-cloudflare-pages)
- [ ] [assets] section in wrangler.toml
- [ ] Static assets uploading correctly
- [ ] nodejs_compat flag enabled
- [ ] All bindings configured (KV, D1, R2)

### UX
- [ ] Feedback widget
- [ ] Feature suggestions
- [ ] Error pages
- [ ] Loading states
- [ ] Mobile responsive

### Performance
- [ ] Core Web Vitals passing
- [ ] Images optimized
- [ ] Proper caching
- [ ] Code splitting

### Accessibility
- [ ] Keyboard navigation
- [ ] Color contrast
- [ ] ARIA labels
- [ ] Focus indicators

"Whatever you do, do it all for the glory of God." — 1 Corinthians 10:31

JESUS CHRIST IS LORD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment