Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# PROJECT KNOWLEDGE BASE

## OVERVIEW
SaaS template built with **SvelteKit (Svelte 5)** + **Tailwind v4** + **DaisyUI** + **Supabase (Auth + Postgres)** + **Stripe (Checkout + Customer Portal)**.

Two major route domains:
- **Marketing** (pre-rendered, SEO-first)
- **Account dashboard** (authenticated, server actions, billing)

## STRUCTURE
```text
./
├── src/
│ ├── hooks.server.ts # Supabase clients + safeGetSession + locals
│ ├── config.ts # SITE NAME/URL/DESCRIPTION + onboarding flag
│ ├── lib/ # mailer, search index, load helpers
│ └── routes/
│ ├── (marketing)/ # public pages: home, pricing, blog, login, search
│ └── (admin)/account/ # dashboard: profile/settings/billing/subscriptions
├── supabase/
│ └── migrations/ # incremental DB changes (preferred after initial setup)
├── database_migration.sql # one-shot initial schema for *new* Supabase projects
├── static/ # favicons/images (served as-is)
└── .github/workflows/ # CI: build/format/lint/check/test
```

## WHERE TO LOOK (TASK → FILES)
| Task | Location | Notes |
|------|----------|-------|
| Rename/brand the site | `src/config.ts`, `src/routes/(marketing)/+layout.svelte`, `static/` | Config drives SEO + sitemap origin.
| Marketing homepage copy/SEO | `src/routes/(marketing)/+page.svelte` | `prerender = true` (static).
| Pricing tiers + Stripe IDs | `src/routes/(marketing)/pricing/pricing_plans.ts` | Replace demo `price_...`/`prod_...`.
| Auth/session plumbing | `src/hooks.server.ts`, `src/routes/(marketing)/login/+layout.ts` | `safeGetSession` validates JWT via `getUser()`.
| OAuth providers + Auth UI theme | `src/routes/(marketing)/login/login_config.ts`, `src/app.css` | DaisyUI variables theme Supabase Auth UI.
| Dashboard (replace demo) | `src/routes/(admin)/account/(menu)/+page.svelte` | Explicit placeholder content.
| Account settings actions | `src/routes/(admin)/account/api/+page.server.ts` | Email/password/profile/delete actions.
| Billing flows | `src/routes/(admin)/account/subscription_helpers.server.ts`, `.../billing/manage/+page.server.ts`, `.../subscribe/[slug]/+page.server.ts` | Stripe Checkout + Portal. No webhook handler in this repo.
| Email sending + templates | `src/lib/mailer.ts`, `src/lib/emails/*.hbs`, `email_docs.md` | Disabled unless env vars set.
| Search index | `src/lib/build_index.ts`, `vite.config.ts`, `src/routes/(marketing)/search/**` | Index built at `vite build`.
| Database schema/RLS | `database_migration.sql`, `supabase/migrations/*` | RLS enabled on all included tables.
| CI commands | `package.json`, `checks.sh`, `.github/workflows/*.yml` | CI uses fake env vars.

## TEMPLATE CUSTOMIZATION ZONES (DON’T MISS)
1. **Brand + SEO**
- `src/config.ts`: `WebsiteName`, `WebsiteBaseUrl`, `WebsiteDescription`.
- `src/routes/(marketing)/**`: per-page `<title>` + `<meta name="description">`.
- `src/routes/(marketing)/sitemap.xml/+server.ts`: uses `WebsiteBaseUrl`.

2. **Navigation + footer links (currently point to upstream + sponsor)**
- `src/routes/(marketing)/+layout.svelte`: header/footer nav + GitHub link + sponsor block.

3. **Auth providers**
- `src/routes/(marketing)/login/login_config.ts`: `oauthProviders` list.
- Supabase Dashboard: redirect URLs + provider secrets.
- `src/routes/(marketing)/auth/callback/+server.js`: auth code exchange/redirect.

4. **Pricing + subscriptions**
- `src/routes/(marketing)/pricing/pricing_plans.ts`: plan IDs, descriptions, and Stripe `price`/`product` IDs.
- `src/routes/(marketing)/pricing/+page.svelte`: FAQ + feature table are demo content.

5. **Replace demo dashboard with your product**
- `src/routes/(admin)/account/(menu)/+page.svelte`: intentionally a placeholder.
- Add your own app routes under `src/routes/(admin)/...` or new groups.

6. **Database model**
- New projects: start with `database_migration.sql`.
- Ongoing changes: add new files in `supabase/migrations/` (don’t keep editing the one-shot file).
- Always enable RLS + add policies for new user-facing tables.

7. **Email (optional, easy to accidentally “turn on”)**
- Setting `PRIVATE_RESEND_API_KEY` enables sending (see `email_docs.md`). Customize templates first.

## CONVENTIONS (PROJECT-SPECIFIC)
- **Marketing pages are pre-rendered** (`export const prerender = true` in several `+page.ts` / `+layout.ts`). If you add server-only/dynamic data, you must disable prerender for that route.
- **Search index is built during `vite build`** via a Vite plugin (`vite.config.ts`). In prod, `/search/api.json` is served from the built artifact.
- **Two Supabase clients**:
- `event.locals.supabase`: SSR client for user-scoped queries (RLS enforced).
- `event.locals.supabaseServiceRole`: service role client for privileged ops (bypasses RLS).

## ANTI-PATTERNS (THIS REPO)
- Don’t put **service role** keys anywhere client-accessible. Keep `$env/static/private` imports server-only.
- Billing customer creation uses **insert (not upsert)** for `stripe_customers` to avoid accidental overwrites.
- There is a known **temporary CSS hack** for Supabase Auth UI button styling in `src/app.css`.

## COMMANDS
```bash
# dev
npm run dev

# format / lint / types
npm run format
npm run lint
npm run check

# tests
npm run test
npm run test_run

# one-shot: mirrors CI locally
chmod +x ./checks.sh
./checks.sh
```

## NOTES
- CI workflows set fake Supabase/Stripe env vars; local dev needs `.env.local` from `.env.example`.
- Stripe integration here is Checkout + Portal; if you need strict subscription state sync, you’ll likely add **Stripe webhooks** as a new `+server.ts` endpoint.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ Created by the folks at [Kiln AI](https://getkiln.ai)! It's the easiest tool for

**[Kiln AI](https://getkiln.ai)** Rapid AI Prototyping and Dataset Collaboration Tool

## AI Agents support

This repo uses [AGENTS.md](https://github.com/agentsmd/agents.md) guiding (plus nested `*/AGENTS.md`) to help AI agents (and humans) navigate the template and find the right customization zones.

- 👍 **Recommended**: [OpenCode](https://github.com/anomalyco/opencode) with [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) plugin (native support for `AGENTS.md` + nested `AGENTS.md`).
- 👌 **Alternatives**: [OpenAI Codex](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli).
- 🤷‍♂️ **Not recommended** _(vendor lock-in)_: **Claude Code**. _If you want to use it anyway, rename all `AGENTS.md` files to `CLAUDE.md`_.

## Demo

You can explore all the features using our fully functional demo [saasstarter.work](https://saasstarter.work).
Expand Down
35 changes: 35 additions & 0 deletions src/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# src/ — APP CORE

## OVERVIEW
Application entry plumbing: Supabase auth/session, shared types/config, and the SvelteKit route tree.

## STRUCTURE
```text
src/
├── hooks.server.ts # event.locals.supabase + service role + safeGetSession
├── config.ts # template knobs (name/base url/description, onboarding flag)
├── DatabaseDefinitions.ts # generated-ish DB types used by Supabase client
├── lib/ # cross-cutting utilities (mailer/search/load helpers)
└── routes/ # filesystem routing
```

## WHERE TO LOOK
- **Auth/session**: `hooks.server.ts`
- **Branding/SEO defaults**: `config.ts`
- **DB typing**: `DatabaseDefinitions.ts`

Related typing glue:
- `app.d.ts`: defines `event.locals` types (supabase clients + session/user)
- `ambient.d.ts`: project ambient types

## CHANGE THESE (TEMPLATE USERS)
- `config.ts`: set your product name, canonical base URL, and description.
- `hooks.server.ts`: if you add custom `locals` or request-wide auth rules, do it here.

## CONVENTIONS / GOTCHAS
- `safeGetSession` is the “trusted” session getter: it calls `getSession()` and then `getUser()` to validate JWT.
- This repo uses a Supabase auth warning suppression shim in `hooks.server.ts` (see inline comment/link). Treat it as version-compat glue.

## ANTI-PATTERNS
- Don’t import `$env/static/private` from any module that can end up client-side.
- Don’t use the service role client for user-facing queries; prefer RLS via `event.locals.supabase`.
30 changes: 30 additions & 0 deletions src/lib/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# src/lib/ — SHARED UTILITIES

## OVERVIEW
Cross-cutting helpers used by routes: email, search indexing, and Supabase session helpers.

## WHERE TO LOOK
- **Email sending**: `mailer.ts`
- Templates: `emails/welcome_email_{text,html}.hbs`
- Setup docs: `/email_docs.md`
- **Search indexing**: `build_index.ts`
- Build hook: `/vite.config.ts` (builds index after bundle)
- Runtime API: `src/routes/(marketing)/search/api.json/+server.ts`
- **Session helper**: `load_helpers.ts` (used in login/account layouts)

## ENV VARS (USED HERE)
- `PRIVATE_RESEND_API_KEY`, `PRIVATE_ADMIN_EMAIL`, `PRIVATE_FROM_ADMIN_EMAIL` (email)
- Supabase/Stripe keys are imported by routes/hooks, not by most `src/lib/*` modules.

## CHANGE THESE (TEMPLATE USERS)
- Email templates + from/subject values (see `email_docs.md`).
- `build_index.ts` exclude list if you add pages you don’t want searchable.

## GOTCHAS
- Email is intentionally “optional”: if env vars are unset, sending is a no-op.
- In dev, `/search/api.json` can build the index on-demand; in prod it expects the prebuilt artifact.
- Search indexing only covers prerendered pages under `.svelte-kit/output/prerendered/pages`.

## ANTI-PATTERNS
- Don’t add heavyweight per-request work into `build_index.ts` pathways; it’s designed for build-time caching.
- Don’t enable email in production without customizing templates/subjects first (welcome email is included by default).
37 changes: 37 additions & 0 deletions src/routes/(admin)/account/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# src/routes/(admin)/account/ — AUTHENTICATED DASHBOARD

## OVERVIEW
Authenticated account area: onboarding (create profile), settings, billing/subscriptions.

## STRUCTURE (HIGH SIGNAL)
```text
src/routes/(admin)/account/
├── +layout.server.ts # provides session + cookies from hooks
├── +layout.ts # creates Supabase client + enforces redirects
├── +layout.svelte # auth state change → invalidate("supabase:auth")
├── api/+page.server.ts # server actions: profile/email/password/delete
├── create_profile/+page.svelte # onboarding form (writes to profiles)
├── select_plan/+page.svelte # uses PricingModule (demo)
├── (menu)/ # sidebar + dashboard + billing + settings pages
├── subscribe/[slug]/+page.server.ts # Stripe Checkout session creation
└── subscription_helpers.server.ts # Stripe customer + subscription lookup
```

## WHERE TO LOOK
- **Auth enforcement**: `src/hooks.server.ts` (sets `locals.session/user` via `safeGetSession`)
- **Client/server Supabase wiring**: `+layout.ts` + `+layout.server.ts`
- **Replace the dashboard**: `(menu)/+page.svelte` (explicit placeholder)
- **Settings UI plumbing**: `(menu)/settings/settings_module.svelte`
- **Billing portal redirect**: `(menu)/billing/manage/+page.server.ts`

## CHANGE THESE (TEMPLATE USERS)
- Replace `(menu)/+page.svelte` with your actual product UI.
- Extend `profiles` fields:
- DB: add migration(s)
- UI: `create_profile/+page.svelte`, `(menu)/settings/edit_profile/+page.svelte`
- Actions: `api/+page.server.ts`
- If your app needs teams/orgs, introduce a new table + RLS policies and adjust queries accordingly.

## GOTCHAS / SECURITY
- `api/+page.server.ts` uses service role for admin operations (e.g. delete user). Keep it server-only.
- Stripe logic here is synchronous API calls; there is **no webhook endpoint** to reconcile async subscription events.
36 changes: 36 additions & 0 deletions src/routes/(marketing)/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# src/routes/(marketing)/ — MARKETING + BLOG + LOGIN

## OVERVIEW
SEO-first public pages: homepage, pricing, blog (with RSS), sitemap, search, contact form, and login UI.

## WHERE TO LOOK
- **Header/footer + nav links**: `+layout.svelte`
- **Homepage**: `+page.svelte` (`prerender = true` in `+page.ts`)
- **Pricing**: `pricing/+page.svelte`, `pricing/pricing_plans.ts`, `pricing/pricing_module.svelte`
- **Blog index + config**: `blog/+page.svelte`, `blog/posts.ts`
- **Blog post SEO wrapper**: `blog/(posts)/+layout.svelte` (OG/Twitter tags)
- **RSS**: `blog/rss.xml/+server.ts`
- **Sitemap**: `sitemap.xml/+server.ts` (excludes admin group)
- **Search**: `search/+page.svelte`, `search/api.json/+server.ts`
- **Login flow**: `login/**`, `login/login_config.ts`, `auth/callback/+server.js`
- **Contact form**: `contact_us/+page.server.ts` (writes to DB + optional admin email)

## CHANGE THESE (MOST COMMON)
1. **Brand links + footer content**
- `+layout.svelte` currently links to upstream GitHub and includes a sponsor block.

2. **SEO + copy**
- `+page.svelte` (home), `pricing/+page.svelte`, `blog/posts.ts` (blog title/description).

3. **Pricing tiers**
- `pricing/pricing_plans.ts`: set plan IDs and Stripe IDs.
- `pricing/+page.svelte`: FAQ + feature table are demo placeholders.

4. **OAuth providers**
- `login/login_config.ts`: `oauthProviders` list.

## GOTCHAS
- Blog posts are **Svelte pages**, not Markdown. Add a post by:
1) adding an entry to `blog/posts.ts`, and
2) creating the matching `blog/(posts)/<slug>/+page.svelte`.
- Search depends on pre-rendered HTML output; missing pages → rebuild (`npm run build`).
34 changes: 34 additions & 0 deletions src/routes/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# src/routes/ — ROUTING MAP

## OVERVIEW
SvelteKit filesystem routing. Route groups in parentheses (e.g. `(marketing)`) are organizational; they do **not** appear in the URL.

## STRUCTURE
```text
src/routes/
├── +layout.svelte # app-wide CSS + top navigation progress bar
├── +error.svelte # error boundary UI
├── (marketing)/ # public pages (mostly prerendered)
└── (admin)/account/ # authenticated dashboard
```

## WHERE TO LOOK
- **Public pages**: `src/routes/(marketing)/...`
- **Authenticated area**: `src/routes/(admin)/account/...`
- **Endpoints**: `**/+server.{ts,js}` (RSS, sitemap, auth callback, search API)

Key endpoints in this repo:
- `src/routes/(marketing)/sitemap.xml/+server.ts`
- `src/routes/(marketing)/blog/rss.xml/+server.ts`
- `src/routes/(marketing)/search/api.json/+server.ts`
- `src/routes/(marketing)/auth/callback/+server.js`

## CONVENTIONS
- `+page.server.ts` / `+server.ts` are server-only; safe for private env vars.
- Several marketing routes set `export const prerender = true` for static generation.

When adding server-only features to a prerendered route, you may need to set `prerender = false` (otherwise build/prerender can break).

## CHANGE THESE (TEMPLATE USERS)
- Decide whether your product needs separate route groups (e.g. `(app)`, `(docs)`).
- Keep marketing routes prerendered unless you truly need per-request server data.
43 changes: 43 additions & 0 deletions supabase/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# supabase/ — DATABASE + RLS

## OVERVIEW
Schema, policies, and migrations for the Supabase Postgres database.

## WHAT EXISTS
Files:
- `database_migration.sql`: one-shot schema for brand-new Supabase projects.
- `migrations/20240730010101_initial.sql`: initial schema + RLS + trigger + storage bucket.
- `migrations/20240731051052_add_unsubscribed_to_profiles.sql`: example incremental migration.

Core tables (public schema):
- `profiles` (user profile fields + `unsubscribed` flag)
- `stripe_customers` (maps app user → Stripe customer id)
- `contact_requests` (contact form submissions)

Security:
- RLS enabled on all included tables.
- `profiles` policies restrict select/insert/update to `auth.uid() = id`.
- `avatars` storage bucket with public read + open upload policy (adjust if you need stricter rules).

Trigger:
- `on_auth_user_created` inserts a row into `profiles` on new auth user.

## WHERE APP CODE TOUCHES THE DB
- User-scoped (RLS enforced): queries via `event.locals.supabase` from `src/hooks.server.ts`.
- Privileged (bypasses RLS): `event.locals.supabaseServiceRole` is used for:
- `contact_requests` inserts (`src/routes/(marketing)/contact_us/+page.server.ts`)
- Stripe customer mapping (`src/routes/(admin)/account/subscription_helpers.server.ts`)
- admin user deletion (`src/routes/(admin)/account/api/+page.server.ts`)

## CHANGE THESE (TEMPLATE USERS)
Typical extensions:
- Add columns to `profiles` (preferences, roles, onboarding state)
- Add domain tables for your product (projects, documents, teams, etc.)

Safe workflow:
1. **New project**: run `database_migration.sql` once.
2. **After that**: create new incremental SQL files under `supabase/migrations/` and apply them in order.
3. Always:
- enable RLS for new tables,
- add policies,
- avoid relying on the service role for normal user flows.