Team playbook · v1 · October 2026

Welcome to Fludd engineering.

This is the one document you need before your first day. It covers what we use, why we chose it, and the exact steps to go from a fresh laptop to your first merged pull request. Everything linked here is live and in use, not a plan.

TeamFounder (Corey) + you
Productapp.fludd.work · fludd.work
ProcessKanban, weekly review
Questionscoreydd2002@gmail.com

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.

  1. Send Corey your GitHub username. You'll be added to the Fludd repo and the Fludd Roadmap board.
  2. Turn on two-factor authentication for GitHub: github.com/settings/security.
  3. Install VS Code, Node.js 22 LTS and Git. See §2.
  4. Clone the repo, open it in VS Code, and accept the recommended extensions.
  5. Create your own free Supabase project and fill in app/.env.local (§2.4).
  6. Run npm run dev and sign up at localhost:3000.
  7. Run the full local check: npm run lint && npm run typecheck && npm test && npm run test:e2e.
  8. Read §6 AI agents and controls before you open Claude Code.
  9. Take the good first issue card 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.

WhereLanguageWhy
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:

ExtensionWhat it's for
Claude CodeOur approved AI agent (§6)
ESLintLint errors inline; auto-fix on save
PrettierFormats on save using app/.prettierrc.json
Tailwind CSS IntelliSenseClass autocomplete from our theme tokens
VitestRun and debug unit tests from the Testing sidebar
Playwright TestRun end-to-end tests with a visible browser
GitHub Actions · GitHub Pull RequestsSee 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.

  1. Create a project at supabase.com/dashboard.
  2. In its SQL Editor, paste and run app/supabase/migrations/0001_init.sql.
  3. Under Authentication → Sign In / Providers → Email, turn Confirm email off (dev only).
  4. 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

BacklogIdeas and requests. Not yet ready.
ReadyClear acceptance criteria, sized, ordered top = next.
In ProgressWIP limit 2 per person
In ReviewPR open, CI green. WIP limit 3 total
DoneMerged and deployed to production

How a day runs

  1. Start of day (async, 2 minutes): post a short update on the issue you're working on: yesterday, today, anything blocking. No standing meeting.
  2. Finish before you start. If In Review has something waiting, review it before pulling new work. Unblocking a card beats starting one.
  3. Pull, don't wait to be assigned. When you have capacity, take the top card in Ready and assign yourself.
  4. Blocked for more than two hours? Say so on the issue and email Corey. Don't silently start something else.

Weekly rhythm

WhenWhatLength
MondayReplenish: review Backlog, write acceptance criteria, order Ready. Triage Dependabot PRs.30 min
FridayReview & 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.example updated 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.
  • CODEOWNERS requests Corey's review automatically.

Controls on main (enforced by GitHub)

RuleSetting
Direct pushesBlocked. Changes arrive only by pull request.
Required status checksquality, e2e, db-rls (and CodeQL analyze) must pass
Up to dateBranch must be current with main before merging
Conversation resolutionAll review comments resolved before merge
HistoryLinear (squash merge only); force-push and branch deletion blocked
ApprovalsFounder 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

FieldValuesMeaning
StatusBacklog · Ready · In Progress · In Review · DoneThe board columns (§3)
PriorityP0 · P1 · P2P0 = customers are affected now, drop everything. P1 = this week. P2 = when there's room.
SizeS · M · LS < 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 #12 in 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

ToolStatusInstall
Claude Code (VS Code extension + CLI)ApprovedVS Code Marketplace · claude.com/product/claude-code
GitHub Copilot, Cursor, other AI IDEsNot approvedOne tool means one set of guardrails to maintain
Pasting repo code into other chatbotsNot allowedCode and customer data stay in approved tools

The rules

  1. 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.
  2. No secrets, no customer data in prompts. Never paste API keys, .env.local contents, or real names, addresses or emails of pool owners.
  3. 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.
  4. 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/ (see app/AGENTS.md).
  5. Read before approving. Don't click “allow” on a command or edit you haven't read.

How the rules are enforced in the repo

FileWhat it does
CLAUDE.mdLoaded into every Claude Code session: branch rules, commit format, checks to run, migration rules.
app/AGENTS.mdNext.js-specific instructions for agents working in app/.
.claude/settings.jsonShared 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 + CIEven if an agent tries, it can't land unreviewed or failing code on main (§4).
GitHub push protectionBlocks 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.

ConcernToolConfigRun it
LintingESLint 9 with next/core-web-vitals + next/typescriptapp/eslint.config.mjsnpm run lint
FormattingPrettier 3 + Tailwind class sorting; eslint-config-prettier so the two never fightapp/.prettierrc.jsonnpm run format · format:check
TypesTypeScript strictapp/tsconfig.jsonnpm run typecheck
Dependency vulnerabilitiesnpm audit (fails CI on high in production deps).github/workflows/ci.ymlnpm audit --omit=dev
Dependency updatesDependabot: weekly PRs for npm (app + root) and GitHub Actions, minor/patch grouped.github/dependabot.ymlTriaged Mondays
Code vulnerabilities (SAST)GitHub CodeQL, security-extended queries, on every PR + weekly.github/workflows/codeql.ymlCode scanning alerts
Leaked secretsGitHub secret scanning + push protectionRepo settingsAutomatic on push
Line endingsGit normalizes to LF.gitattributesAutomatic

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_KEY goes through src/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.

LayerFrameworkWhereRunIn CI?
Unit: pure logic (readings, parsing, URLs)Vitestapp/src/**/*.test.ts, next to the codenpm test · npm run test:watch✅ quality
Database security: row-level security between companiesSQL assertions on plain PostgreSQL 16app/supabase/tests/see below✅ db-rls
End-to-end smoke: real browser against both sitesPlaywright (Chromium, desktop + Pixel 7)app/e2e/*.spec.tsnpm run build && npm run test:e2e✅ e2e
Integration: full flows against a live Supabase projectNode scriptsapp/scripts/check-*.mjsnpm run check:phase1 … check:phase6Manual, 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

ServiceUsed forYour access
GitHubCode, issues, board, CI, security alertsWrite (2FA required)
VercelHosting for both sites, preview deploymentsViewer: see builds and logs
SupabaseProduction Postgres, Auth, photo storageYour own dev project. Production is founder-only.
ResendTransactional email (visit reports, early-access form)Founder-only

Deployment

One repo, two Vercel projects. Nobody deploys by hand.

ProjectRoot dirDomainBuild
fludd-landing/fludd.workNone (static + api/ function)
fludd-appapp/app.fludd.worknext build
  1. Every PR gets a preview URL for each project. Test there.
  2. Merge to main deploys production automatically, about a minute later.
  3. Verify the change on the production URL, then move the card to Done.
  4. 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.example files 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-landing isn't visible to fludd-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_KEY bypasses all row-level security. It is imported only through src/lib/env.server.ts, which is guarded by server-only.
  • Never send a secret over email, text or chat.
If a secret leaks (committed, pasted, screenshotted): tell Corey immediately, rotate it first in the provider's dashboard, update Vercel, redeploy, and only then clean up the history. Deleting the commit doesn't un-leak a key.

Database changes

  1. Add a new numbered file, e.g. app/supabase/migrations/0002_add_photo_captions.sql. Never edit a migration that has already shipped.
  2. Every new table enables RLS and gets policies scoped by current_company_id(). anon is granted nothing.
  3. Add RLS assertions; regenerate database.types.ts; apply to your dev project.
  4. 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.

SeverityExampleResponse
P0App down; one company can see another's data; secret leakedDrop everything. Roll back, email Corey, open a P0 issue, write a short post-mortem within 48 hours.
P1A feature broken for some users; emails not sendingFix the same day, through a normal PR
P2Cosmetic, has a workaroundGoes 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 in app/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.