Day-one checklist
Work through this list in order. Each item links to the section that explains it. By the end of the day you should have the app running locally and a small PR open.
- Send Corey your GitHub username. You'll be added to the Fludd repo and the Fludd Roadmap board.
- Turn on two-factor authentication for GitHub: github.com/settings/security.
- Install VS Code, Node.js 22 LTS and Git. See §2.
- Clone the repo, open it in VS Code, and accept the recommended extensions.
- Create your own free Supabase project and fill in
app/.env.local(§2.4). - Run
npm run devand sign up atlocalhost:3000. - Run the full local check:
npm run lint && npm run typecheck && npm test && npm run test:e2e. - Read §6 AI agents and controls before you open Claude Code.
- Take the
good first issuecard from Ready, branch, and open your first PR (§4).
Language selection
We write TypeScript everywhere it runs on a build step, plain HTML/CSS/JavaScript where it doesn't, and SQL for the database.
| Where | Language | Why |
|---|---|---|
app/: the tech app and public report |
TypeScript 5 (strict: true) with React 19 on Next.js 16 |
One language for UI, server actions and email templates. Next.js and Supabase are TypeScript-first. Database types are generated into src/lib/supabase/database.types.ts, so a schema change becomes a compile error, not a 2 a.m. bug. |
Repo root: marketing site fludd.work |
Hand-written HTML, CSS, vanilla JS; one Node serverless function (api/subscribe.js) |
A landing page doesn't need a framework. No build step means nothing to break, a strict Content Security Policy, and a near-instant page. |
app/supabase/ |
PostgreSQL SQL | The schema and every row-level-security rule live in migrations, so access control is code that gets reviewed and tested (§8). |
| Styling | Tailwind CSS v4 (app); plain CSS with tokens in :root (landing) |
Design tokens live in app/src/app/globals.css. Shared components live in app/src/components/ui.tsx. |
Rule of thumb: no new languages or frameworks without a written proposal on the board. Every extra tool is something the next hire has to learn.
IDE selection and setup
We use Visual Studio Code. The repo ships its settings and extension list, so your editor formats, lints and type-checks exactly the way CI does.
2.1 Install
2.2 Clone and open
git clone https://github.com/coreydd2002/Fludd.git
cd Fludd
code .
VS Code will show “This workspace has extension recommendations.” Click Install All. The list lives in .vscode/extensions.json:
| Extension | What it's for |
|---|---|
| Claude Code | Our approved AI agent (§6) |
| ESLint | Lint errors inline; auto-fix on save |
| Prettier | Formats on save using app/.prettierrc.json |
| Tailwind CSS IntelliSense | Class autocomplete from our theme tokens |
| Vitest | Run and debug unit tests from the Testing sidebar |
| Playwright Test | Run end-to-end tests with a visible browser |
| GitHub Actions · GitHub Pull Requests | See CI status and review PRs without leaving the editor |
The extension list also marks GitHub Copilot as unwanted, since it isn't an approved tool (§6).
2.3 What the workspace settings do
.vscode/settings.json is committed, so you don't configure anything by hand:
- Format on save with Prettier, and ESLint fix on save.
- ESLint runs in the
app/folder; TypeScript uses the project's own version (accept the “Use Workspace Version” prompt). - LF line endings, final newline, trim trailing whitespace. These match
.gitattributes, so Windows and CI agree.
2.4 Run the app locally
Use your own free Supabase project for development. You get a database nobody else touches, and no keys ever have to be sent to you.
- Create a project at supabase.com/dashboard.
- In its SQL Editor, paste and run
app/supabase/migrations/0001_init.sql. - Under Authentication → Sign In / Providers → Email, turn Confirm email off (dev only).
- Then, in a terminal:
cd app
npm ci # exact versions from package-lock.json
cp .env.example .env.local # then fill in the three SUPABASE_* values
npx playwright install chromium
npm run dev # http://localhost:3000
npm run make:user # optional: a ready-made dev login
The variables are described in app/.env.example. Leave the email ones blank until you work on email.
To preview the marketing site: cd app && node e2e/static-server.mjs → localhost:4000. To test its form against Resend, use vercel dev at the repo root (see the root README).
Development strategy
We run Kanban: work flows continuously across one board, and we limit how much is in progress at once, not how much is planned per sprint.
Why Kanban, not Scrum
We're a founder plus one developer, building for pool-service owners whose feedback arrives whenever they have it. Two-week sprint commitments would be broken by the first customer bug. Kanban lets us re-prioritize daily while still finishing what we start.
The board
How a day runs
- Start of day (async, 2 minutes): post a short update on the issue you're working on: yesterday, today, anything blocking. No standing meeting.
- Finish before you start. If In Review has something waiting, review it before pulling new work. Unblocking a card beats starting one.
- Pull, don't wait to be assigned. When you have capacity, take the top card in Ready and assign yourself.
- Blocked for more than two hours? Say so on the issue and email Corey. Don't silently start something else.
Weekly rhythm
| When | What | Length |
|---|---|---|
| Monday | Replenish: review Backlog, write acceptance criteria, order Ready. Triage Dependabot PRs. | 30 min |
| Friday | Review & retro: demo what reached Done, look at cards that aged, agree on one process tweak. | 30 min |
Definition of Ready
- Written as who/why + acceptance criteria (the issue template asks for both)
- Sized S or M; L gets split
- No open questions for the founder
Definition of Done
- Acceptance criteria met, with tests
- All CI checks green; PR squash-merged
- Verified on the production URL
- Docs /
.env.exampleupdated if behavior or config changed
Git strategy and controls
We use GitHub Flow: main is always deployable, all work happens on short-lived branches, and everything reaches main through a reviewed, green pull request.
Repository: github.com/coreydd2002/Fludd
Branches
git switch main && git pull
git switch -c feat/12-photo-captions # type/issue-number-short-slug
Types: feat/ new behavior · fix/ bug · chore/ tooling, deps · docs/ docs only. Branches should live under three days. If yours is getting bigger, split the card.
Commit messages: Conventional Commits
feat(app): add captions to visit photos
fix(report): hide internal notes on the public report
chore(deps): bump next to 16.3.7
docs: explain RLS test setup in the playbook
Format: type(scope): imperative summary, under 72 characters. Scopes we use: app, landing, report, db, ci, deps. The body explains why. PRs are squash-merged, so the PR title becomes the commit on main and must follow the same format.
Pull requests
- Open early as a draft if you want feedback; mark ready when CI is green.
- Fill in the PR template: what and why,
Closes #12, how you tested it, the safety checklist, and the AI-assistance box. - Every PR gets a Vercel preview URL in a comment. Click through it.
CODEOWNERSrequests Corey's review automatically.
Controls on main (enforced by GitHub)
| Rule | Setting |
|---|---|
| Direct pushes | Blocked. Changes arrive only by pull request. |
| Required status checks | quality, e2e, db-rls (and CodeQL analyze) must pass |
| Up to date | Branch must be current with main before merging |
| Conversation resolution | All review comments resolved before merge |
| History | Linear (squash merge only); force-push and branch deletion blocked |
| Approvals | Founder reviews every PR. A required second approval switches on when the team reaches two developers. |
See them yourself: Settings → Branches (admin view).
Issue tracking
All work is tracked in GitHub Issues and visualized on a GitHub Projects board, right next to the code.
Card fields
| Field | Values | Meaning |
|---|---|---|
| Status | Backlog · Ready · In Progress · In Review · Done | The board columns (§3) |
| Priority | P0 · P1 · P2 | P0 = customers are affected now, drop everything. P1 = this week. P2 = when there's room. |
| Size | S · M · L | S < half a day, M = 1–2 days, L = must be split before Ready |
Labels
feature bug chore docs security good first issue
Automation
- New issues and PRs are added to the board automatically.
- Writing
Closes #12in a PR moves card #12 to Done when the PR merges. - Blank issues are disabled. Use the Bug or Feature / task form so every card has acceptance criteria or reproduction steps.
AI agents and controls
We are an AI-assisted team, deliberately. Claude Code is our one approved AI tool. The controls below exist so AI makes us faster without making us careless.
Approved
| Tool | Status | Install |
|---|---|---|
| Claude Code (VS Code extension + CLI) | Approved | VS Code Marketplace · claude.com/product/claude-code |
| GitHub Copilot, Cursor, other AI IDEs | Not approved | One tool means one set of guardrails to maintain |
| Pasting repo code into other chatbots | Not allowed | Code and customer data stay in approved tools |
The rules
- You own every line. AI-written code gets the same review as yours. Don't open a PR containing code you can't explain. Tick the AI-assistance box in the PR template.
- No secrets, no customer data in prompts. Never paste API keys,
.env.localcontents, or real names, addresses or emails of pool owners. - The agent works on branches only. It doesn't push to
main, force-push, deploy, or change Vercel or Supabase settings. A human opens and merges the PR. - Verify, don't trust. The agent must run lint, typecheck and tests before claiming it's done, and CI checks the same things again. Next.js 16 differs from most AI training data, so check new APIs against
app/node_modules/next/dist/docs/(seeapp/AGENTS.md). - Read before approving. Don't click “allow” on a command or edit you haven't read.
How the rules are enforced in the repo
| File | What it does |
|---|---|
CLAUDE.md | Loaded into every Claude Code session: branch rules, commit format, checks to run, migration rules. |
app/AGENTS.md | Next.js-specific instructions for agents working in app/. |
.claude/settings.json | Shared permissions. Denied: reading .env/.env.local and *.pem, git push --force, pushing to main, git reset --hard, vercel --prod, vercel env, supabase db push/reset. Always asks: any git push, gh pr merge, npm install. |
| Branch protection + CI | Even if an agent tries, it can't land unreviewed or failing code on main (§4). |
| GitHub push protection | Blocks pushes that contain recognizable secrets (§7). |
Personal preferences go in .claude/settings.local.json, which is git-ignored. You can tighten the shared rules there but never loosen them.
Code quality
Every tool below runs in your editor or with one command, and again in CI on every pull request. If CI is red, the PR doesn't merge.
| Concern | Tool | Config | Run it |
|---|---|---|---|
| Linting | ESLint 9 with next/core-web-vitals + next/typescript | app/eslint.config.mjs | npm run lint |
| Formatting | Prettier 3 + Tailwind class sorting; eslint-config-prettier so the two never fight | app/.prettierrc.json | npm run format · format:check |
| Types | TypeScript strict | app/tsconfig.json | npm run typecheck |
| Dependency vulnerabilities | npm audit (fails CI on high in production deps) | .github/workflows/ci.yml | npm audit --omit=dev |
| Dependency updates | Dependabot: weekly PRs for npm (app + root) and GitHub Actions, minor/patch grouped | .github/dependabot.yml | Triaged Mondays |
| Code vulnerabilities (SAST) | GitHub CodeQL, security-extended queries, on every PR + weekly | .github/workflows/codeql.yml | Code scanning alerts |
| Leaked secrets | GitHub secret scanning + push protection | Repo settings | Automatic on push |
| Line endings | Git normalizes to LF | .gitattributes | Automatic |
Before you push
cd app
npm run lint && npm run typecheck && npm run format:check && npm test
If Prettier complains, run npm run format. Format-on-save in VS Code usually means you never see this.
Review checklist (what Corey looks for)
- Does it match the surrounding code: naming, comment density, existing components in
ui.tsx? - Server-only code stays server-only. Anything using
SUPABASE_SERVICE_ROLE_KEYgoes throughsrc/lib/supabase/admin.ts, and its output is narrowed before it reaches a page. - The public report (
/r/[token]) never shows last name, address, email or internal notes. - Loading, error and empty states exist for anything that fetches.
Testing frameworks
Four layers, from fast and many to slow and few. A change ships with tests at the lowest layer that can catch its bug.
| Layer | Framework | Where | Run | In CI? |
|---|---|---|---|---|
| Unit: pure logic (readings, parsing, URLs) | Vitest | app/src/**/*.test.ts, next to the code | npm test · npm run test:watch | ✅ quality |
| Database security: row-level security between companies | SQL assertions on plain PostgreSQL 16 | app/supabase/tests/ | see below | ✅ db-rls |
| End-to-end smoke: real browser against both sites | Playwright (Chromium, desktop + Pixel 7) | app/e2e/*.spec.ts | npm run build && npm run test:e2e | ✅ e2e |
| Integration: full flows against a live Supabase project | Node scripts | app/scripts/check-*.mjs | npm run check:phase1 … check:phase6 | Manual, before a release (needs real keys) |
Which test do I write?
- Changed a function in
src/lib/? Add a Vitest case. Example:readings.test.ts. - Changed the schema or a policy? Add assertions to
01_rls_test.sql, including one proving another company can't see the data. - New page or route? Add a Playwright smoke test if it renders without a login. Otherwise cover the flow in a
check:*script. - Bug fix? First write a test that fails because of the bug, then fix it.
Running the RLS tests locally
createdb fludd_test
psql -d fludd_test -v ON_ERROR_STOP=1 -f app/supabase/tests/00_supabase_stub.sql
psql -d fludd_test -v ON_ERROR_STOP=1 -f app/supabase/migrations/0001_init.sql
psql -d fludd_test -v ON_ERROR_STOP=1 -f app/supabase/tests/01_rls_test.sql
No local Postgres? Push your branch. The db-rls job runs exactly these commands in a postgres:16 container.
Other controls, tools and procedures
Accounts you'll be given
| Service | Used for | Your access |
|---|---|---|
| GitHub | Code, issues, board, CI, security alerts | Write (2FA required) |
| Vercel | Hosting for both sites, preview deployments | Viewer: see builds and logs |
| Supabase | Production Postgres, Auth, photo storage | Your own dev project. Production is founder-only. |
| Resend | Transactional email (visit reports, early-access form) | Founder-only |
Deployment
One repo, two Vercel projects. Nobody deploys by hand.
| Project | Root dir | Domain | Build |
|---|---|---|---|
fludd-landing | / | fludd.work | None (static + api/ function) |
fludd-app | app/ | app.fludd.work | next build |
- Every PR gets a preview URL for each project. Test there.
- Merge to
maindeploys production automatically, about a minute later. - Verify the change on the production URL, then move the card to Done.
- Roll back if production breaks: Vercel → project → Deployments → the last good deployment → Promote to Production. This takes seconds, needs no code, and is always the first move. Fix forward afterwards with a normal PR.
Secrets and environment variables
- Real values live only in Vercel (Project → Settings → Environment Variables) and in your local, git-ignored
.env.local. .env.examplefiles are committed with names and comments only. Check them before every commit that touches them.- Variables don't cross projects. A key set on
fludd-landingisn't visible tofludd-app. When something env-related fails, check which project holds it first. NEXT_PUBLIC_*values are compiled into the browser bundle at build time. Changing one requires a redeploy, and they must never hold anything secret.SUPABASE_SERVICE_ROLE_KEYbypasses all row-level security. It is imported only throughsrc/lib/env.server.ts, which is guarded byserver-only.- Never send a secret over email, text or chat.
Database changes
- Add a new numbered file, e.g.
app/supabase/migrations/0002_add_photo_captions.sql. Never edit a migration that has already shipped. - Every new table enables RLS and gets policies scoped by
current_company_id().anonis granted nothing. - Add RLS assertions; regenerate
database.types.ts; apply to your dev project. - After merge, the founder applies the migration to production before the code that depends on it is promoted.
Incidents and on-call
We're pre-launch, so there is no pager rotation yet. The founder is on call. Once you've shipped your first month, you join a weekday-hours rotation.
| Severity | Example | Response |
|---|---|---|
| P0 | App down; one company can see another's data; secret leaked | Drop everything. Roll back, email Corey, open a P0 issue, write a short post-mortem within 48 hours. |
| P1 | A feature broken for some users; emails not sending | Fix the same day, through a normal PR |
| P2 | Cosmetic, has a workaround | Goes on the board |
First places to look: Vercel status · Supabase status · Resend status · Vercel → Logs for the affected project.
Security baseline
- 2FA on GitHub, Vercel and Supabase.
- The landing page ships a strict Content Security Policy in
vercel.json: no inline scripts or third-party JavaScript. The app sets its own inapp/next.config.ts. - Customer photos live in a private bucket and reach reports only as short-lived signed URLs.
- Report a vulnerability privately to coreydd2002@gmail.com, not in a public issue.
Glossary
- Tech
- A pool technician, our primary app user.
- Company
- A pool-service business, the tenant boundary every RLS policy enforces.
- Visit
- One service stop: checklist, chemical readings, photos, notes.
- Report
- The public page a pool owner receives at
/r/[token]after a visit. - RLS
- Postgres row-level security. Data isolation between companies is enforced here.
Something here wrong or out of date? That's a bug: file it, or edit playbook.html in a docs/ PR.