This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
StudyTutors is a SvelteKit-powered static site for StudyTutors e.V., a German nonprofit providing free tutoring to refugees and underprivileged children. The site integrates with Contentful CMS, Airtable for forms, and Mapbox for location services.
- Framework: SvelteKit 2.x with static adapter
- Language: TypeScript 5.8+
- Package Manager: pnpm
- Node Version: 20.x
- Deployment: Netlify (automatic on main branch)
- Repository: https://github.com/sbsev/site
# Install pre-commit hooks (optional but recommended)
pre-commit install
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env
# Then edit .env with your Contentful credentials# Start dev server (http://localhost:3000)
pnpm dev
# Type check (svelte-check)
pnpm check
# Lint with auto-fix
pnpm lint
# Lint production code only
pnpm lint:prod# Full test suite (13+ test files)
pnpm test
# Smoke tests only (quick sanity checks)
pnpm test:smoke
# Run tests with visible browser UI
pnpm test:headed
# Debug mode with inspector
pnpm test:debug
# Test configuration: playwright.config.ts
# Base URL: http://localhost:3005 (auto-started)
# Browsers: Chromium, Firefox, WebKit
# Timeout: 30s per test# Production build (outputs to /build/)
pnpm build
# Preview the production build locally
pnpm preview
# Combined build + preview
pnpm servesrc/
├── app.html # HTML shell (Plausible analytics, SVG symbols)
├── app.css # Global styles
├── app.d.ts # TypeScript definitions
├── hooks.server.ts # Server-side hooks (Algolia disabled)
├── lib/ # Reusable components and utilities
│ ├── *.svelte # 24+ Svelte components (Header, Footer, Modal, etc.)
│ ├── fetch.ts # Contentful + Airtable GraphQL queries
│ ├── types.ts # TypeScript interfaces (Chapter, Post, Page, etc.)
│ ├── stores.ts # Svelte stores
│ └── index.ts # Component barrel export
├── routes/ # SvelteKit page routes (file-based routing)
│ ├── +layout.svelte # Root layout component
│ ├── +layout.ts # Layout data loader (all pages)
│ ├── +page.svelte # Home page
│ ├── +page.server.ts # Home page server-side logic
│ ├── +error.svelte # Error page
│ ├── [...slug]/ # Catch-all dynamic routes
│ ├── blog/ # Blog listing and individual posts
│ ├── signup-pupil/ # Student signup form
│ ├── signup-student/ # Tutor/volunteer signup form
│ ├── standorte/ # Chapters/locations listing and details
│ └── [other routes]/ # Additional pages (faq, presse, lernmaterial, etc.)
├── signup-form/ # Form configurations (YAML)
│ ├── de/ # German forms
│ └── us/ # US forms (optional)
└── utils/ # Utility functions
├── actions.ts # Form action handlers (API routes)
├── contentful.js # Contentful SDK
├── marked.ts # Markdown to HTML conversion
└── algolia.ts # Algolia search (indexing currently disabled)
static/ # Static assets (images, fonts, manifest)
tests/ # Playwright end-to-end tests
.github/workflows/ # CI/CD pipelines (test.yml, lighthouse.yml)
- Content Source: Contentful CMS (GraphQL API)
- Form Submissions: Airtable (BaseQL)
- Page Routes: SvelteKit file-based routing with catch-all
[...slug]for CMS content - Data Loading:
+layout.tsfetches global data;+page.ts/+page.server.tsfetch page-specific data - Static Generation: Pre-rendered HTML at build time via static adapter
- Deployment: Netlify continuous deployment on main branch push
Layout/Navigation:
Header.svelte,Nav.svelte,Footer.svelte- Main layout structureBasePage.svelte- Base layout for content pages
Interactive:
Modal.svelte- Modal dialogsChapterMap.svelte,Map.svelte,Geocoder.svelte- Mapbox integrationPlaceSelect.svelte- Location selectionCollapsible.svelte,Toggle.svelte,RadioButtons.svelte- Form controlsThemeSwitcher.svelte- Dark/light mode toggle
Content Display:
PostPreview.svelte- Blog post cardsChapterList.svelte- Chapter listingsSearchHit.svelte- Search resultsImg.svelte- Optimized images with placeholdersTagList.svelte,Social.svelte,ToolTip.svelte- Utility components
Forms:
FormField.svelte- Reusable form input wrapper
lib/fetch.ts - Primary data layer:
contentful_fetch(query, variables)- GraphQL queries to Contentfulairtable_fetch(query, variables)- GraphQL queries to Airtablefetch_chapters()- Loads all chapter/location database64_thumbnail(imageUrl)- Generates low-quality image placeholders
Example usage:
// In +page.server.ts or +layout.ts
import { contentful_fetch, fetch_chapters } from '$lib'
export async function load({ params }) {
const chapters = await fetch_chapters()
const pageData = await contentful_fetch(query, { slug: params.slug })
return { chapters, pageData }
}Key TypeScript interfaces:
Chapter- Location/chapter informationPage- CMS pagePost- Blog post with authorImage- Image metadataAuthor- Author profileBlogTag- Blog tag enumForm- Form configuration
Copy .env.example to .env and configure:
# Contentful CMS
VITE_CONTENTFUL_SPACE_ID=<your-space-id>
VITE_CONTENTFUL_ACCESS_TOKEN=<your-access-token>
CONTENTFUL_MANAGEMENT_TOKEN=<for-contentful-cli-only>
# Airtable (forms and chapter data)
VITE_AIRTABLE_API_KEY=<your-api-key>
VITE_AIRTABLE_CHAPTER_BASE_APP_ID=<base-id>
# Mapbox (maps and geocoding)
VITE_MAPBOX_PUBLIC_KEY=<your-public-key>
# Algolia (search - currently disabled)
VITE_ALGOLIA_APP_ID=<optional>
VITE_ALGOLIA_SEARCH_KEY=<optional>
# Translation services
DEEPL_API_KEY=<optional>
# Analytics and monitoring
VITE_PLAUSIBLE_API_KEY=<optional>
LHCI_GITHUB_APP_TOKEN=<for-lighthouse-ci>-
ESLint: Enforced on all files (production-only in CI)
- No semicolons, single quotes, 2-space indent
- No console logs in production (warn/error allowed)
- Unused variables must be prefixed with
_ - Test files have relaxed rules
-
Prettier: Formats code automatically
- 90-character print width for Svelte
- No semicolons, single quotes
-
Pre-commit hooks (optional but recommended)
- Prettier formatting
- ESLint auto-fix
- Spell checking (codespell)
- YAML validation
- Case conflict detection
Run linting:
pnpm lint # Auto-fix all files
pnpm lint:prod # Production code onlypnpm check # svelte-check type validation- Strict TypeScript mode enabled
- All Svelte components should have
<script lang="ts"> - Define types in
lib/types.tsfor reusable interfaces
Framework: Playwright 1.54+ for end-to-end testing
Test Files (in tests/):
smoke.test.ts- Quick sanity checksaccessibility.test.ts- A11y validationcore-functionality.test.ts- Main featuresblog.test.ts,pages.test.ts- Content routingforms.test.ts,pupil-form.test.ts,student-form.test.ts- Form handlingconsole-capture.test.ts- Console error detection
Test Helpers (tests/helpers.ts):
- Utility functions available for your tests
Running Tests:
pnpm test # Full suite (all browsers)
pnpm test:smoke # Smoke tests only
pnpm test:headed # With browser UI visible
pnpm test:debug # Debug mode
# Configuration: playwright.config.ts
# Base URL: http://localhost:3005
# Auto-starts dev server on port 3005
# Runs: Chromium, Firefox, WebKit
# Retry: 2 times in CICI/CD Testing (.github/workflows/test.yml):
- Runs on push to main and pull requests
- Runs: ESLint (prod only) → Type check → Playwright tests
- Uploads test artifacts (7-day retention)
- Must pass before merge to main
Two main signup flows with configuration in src/signup-form/:
Pupil Signup (routes/signup-pupil/):
- Student seeking tutoring
- Form config:
signup-form/de/pupil.yml - Submits to Airtable
Student Signup (routes/signup-student/):
- Volunteer tutor/mentor
- Form config:
signup-form/de/student.yml - Submits to Airtable
Forms use:
- YAML configuration for fields (language-specific)
utils/actions.tsfor form handlers (server actions)FormField.sveltefor input components- Airtable as backend storage
Markdown Rendering: utils/marked.ts
- Converts markdown to HTML
- Custom styling for code blocks, links, etc.
- Used in blog posts and CMS content
Currently German-focused (de) with optional US (us) support:
src/signup-form/de/- German formssrc/signup-form/us/- US forms (optional)- Language detection/selection in Header component
Translation utilities available:
- DeepL API (utils/deeplTranslate.js)
- Google Translate API (utils/googleTranslate.js)
Automatically runs on every push/PR (.github/workflows/lighthouse.yml):
- Performance, Accessibility, Best Practices, SEO scores
- Results displayed on commits and PRs
- Plausible Analytics: Privacy-focused, ad-blocker resistant
- Proxied through Netlify
- Initialized in
src/app.html
- Static site generation = fast delivery via CDN
- Image optimization via
base64_thumbnail()(low-quality placeholders) Img.sveltecomponent for responsive images- No runtime overhead (fully pre-rendered)
Main branch pushes automatically deploy to Netlify (via netlify.toml):
- Build:
pnpm build - Publish:
build/directory - Node 18.16.0
Domain redirects (Netlify):
sbsev.netlify.com→studytutors.destudenten-bilden-schueler.at→studytutors.atstudenten-bilden-schueler.de→studytutors.de
# Create production build
pnpm build
# Preview locally
pnpm preview
# Deploy via Netlify CLI
netlify deploy
# Deploy to production
netlify deploy --prod- Create route directory:
src/routes/new-page/ - Create
+page.svelte(component) - Optionally create
+page.tsor+page.server.ts(data loading) - Import
BasePage.sveltefor consistent layout - Use
contentful_fetch()orairtable_fetch()for data
Example:
<!-- src/routes/new-page/+page.svelte -->
<script lang="ts">
import BasePage from '$lib/BasePage.svelte';
export let data; // from +page.ts
</script>
<BasePage>
<h1>{data.title}</h1>
<!-- content here -->
</BasePage>- Create in
src/lib/ComponentName.svelte - Use
<script lang="ts">with type annotations - Export to
src/lib/index.tsbarrel export - Use in routes and other components
Content is fetched via GraphQL in +page.ts or +layout.ts:
// src/routes/example/+page.ts
import { contentful_fetch } from '$lib'
const query = `
query GetPage($slug: String!) {
pageCollection(where: { slug: $slug }) {
items {
title
content
}
}
}
`
export async function load({ params }) {
const data = await contentful_fetch(query, { slug: params.slug })
return data.pageCollection.items[0]
}- Create form config YAML:
src/signup-form/de/my-form.yml - Create route:
src/routes/my-form/+page.svelte - Create form handler in
utils/actions.ts - Use
FormField.sveltecomponents - Wire up to Airtable in form handler
- Dev Tools: Browser DevTools with Svelte Inspector extension
- Console: Check for errors/warnings (ESLint prevents console logs in prod)
- Test Debug:
pnpm test:debugwith inspector - Test Browser:
pnpm test:headedto watch tests run
Current branch: dev/update
Main branch: main (protected, requires PR)
Working with pre-commit hooks:
# Install hooks
pre-commit install
# Hooks run automatically on git commit:
# - Prettier formatting
# - ESLint auto-fix
# - Spell checking
# - YAML validation
# Bypass hooks if necessary (not recommended)
git commit --no-verifyDev server won't start:
# Clear cache and reinstall
rm -rf node_modules .svelte-kit
pnpm install
pnpm devTests failing:
# Ensure Contentful env vars are set
# Run smoke tests first
pnpm test:smoke
# Run with headed browser to see issues
pnpm test:headed
# Check console errors
pnpm test tests/console-capture.test.tsLinting errors:
# Auto-fix all issues
pnpm lint
# Check types
pnpm checkBuild fails:
# Ensure env vars include all required keys from .env.example
# Check that Contentful/Airtable credentials are valid
pnpm build --verboseGitHub Actions (.github/workflows/):
test.yml - Runs on PR and push to main:
- Setup Node 20.x + pnpm
- Install dependencies
- ESLint check (production code only)
- Type check (svelte-check)
- Build with secrets
- Playwright test suite (all browsers)
- Upload artifacts (7 days)
lighthouse.yml - Runs on PR and push to main:
- Build site
- Run Lighthouse CI
- Display performance results
Required secrets in GitHub:
VITE_CONTENTFUL_SPACE_IDVITE_CONTENTFUL_ACCESS_TOKENVITE_AIRTABLE_API_KEYVITE_AIRTABLE_CHAPTER_BASE_APP_IDVITE_MAPBOX_PUBLIC_KEYLHCI_GITHUB_APP_TOKEN