A chatbot that provides legal information related to housing and eviction in Oregon.
Live at https://tenantfirstaid.com/
💡 Using Claude Code? Type /onboarding in the Claude Code UI for guided setup assistance. |
|---|
GitHub account
- You will need a GitHub account (free) to contribute to the project. No account is necessary to browse the source code.
- You will be invited to join the Contributor team after you complete step 2 ("Connect on Discord & Request Access") of the Code PDX onboarding
- Look for the invitation email and click the link in the email to accept the invitation.
- You will also have to enable commit signing by adding a key (typically
GPG) to your GitHub account (click on your avatar -> Settings -> SSH and GPG keys).
mise
- This repo is a mise monorepo.
miseprovisions and pins the rest of the toolchain for you —uv(backend Python deps/tools) andnode/npm(frontend) — and wires up the dev tasks used throughout this README. Install mise
Google Cloud application default credentials file
- This is needed to spin up a local instance of the backend (i.e. API calls to the chat LLM and RAG agent).
- The chatbot now uses Google Gemini (previously OpenAI's ChatGPT).
- The
tenantfirstaidGoogle project admin will need to manually assign a role to you (gmail account). Reach out in the Discord channel #tenantfirstaid-general to arrange this. - You need to authenticate with the gcloud CLI to develop.
gcloudis pinned as a per-task tool in the rootmise.toml, so it's provisioned on first use — no separate install:mise run //:gcloud-login(root-qualified) — runsgcloud auth application-default login+set-quota-project, then prints the resulting application default credentials file path- add the printed path as
GOOGLE_APPLICATION_CREDENTIALS=<PATH_TO_CREDS>to yourbackend/.envfile
LangChain/LangSmith
- langsmith Developer (free) or Plus account and API key
- clone repo
- copy
backend/.env.exampleto a new file named.envin the same directory.- set
GOOGLE_APPLICATION_CREDENTIALSas per Google Cloud application default credentials file (requires the project admin to have already granted your Google account access, per that section) - set
LANGSMITH_API_KEYas per LangChain/LangSmith - set
VERTEX_AI_DATASTORE_LAWSas per the production example inbackend/.env.example:20
- set
mise run setup(from the repo root; one-time: provisions the backend/frontend toolchains, installs deps, and generates frontend assets)- on a fresh clone mise will prompt to trust the repo's config — run
mise trustif prompted
- on a fresh clone mise will prompt to trust the repo's config — run
- (optional) smoke-test your Google Cloud credentials before starting the app:
mise run //:gcloud-login-check— it loadsGOOGLE_APPLICATION_CREDENTIALSfrombackend/.envthe same way the app does and queries the same Vertex AI Search serving config mise run dev(starts the backend API and frontend dev server together)- or in two separate terminals:
mise run //backend:serveandmise run //frontend:dev
- or in two separate terminals:
- Go to http://localhost:5173
- Start chatting
💡 Using Claude Code? Type /backend in the Claude Code UI for backend workflow reference. |
|---|
- change to the
backend/directory% cd backend
-
run individual checks
-
format Python code with
ruff% mise run fmt
-
lint Python code with
ruff% mise run lint
-
typecheck Python code with
ty% mise run typecheck
typecheck with other Python typecheckers which are not protected in PR Checks - useful for completeness & a 2nd opinion
- typecheck Python code with
mypy% mise run typecheck --checker mypy
- typecheck Python code with
pyrefly% mise run typecheck --checker pyrefly
- typecheck Python code with
-
test Python code with
pytest% mise run test
To pass extra flags straight through to the underlying tool,
lint,typecheck, andtestall accept trailing args after--, e.g.% mise run lint -- --fix % mise run test -- -k test_some_name -vThis is preferred over
mise exec -- uv run <tool> ..., which bypasses thesyncdependency and can silently run against a stale.venvafter a dependency bump. -
-
or run the above checks in one-shot
% mise run check
(equivalent to
mise run //backend:checkfrom the repo root).checkrunslint,typecheck, andtestconcurrently (afterfmt), so all three report even if one fails — you see every failure in a single run rather than stopping at the first.To run both backend and frontend checks together, use
mise run checkfrom the repo root. -
build and browse the backend user guide (needs Quarto, or add
--container)% mise run docs % mise run docs-serve
| 💡 On MacOS
docs-servewill open Safari but can't access the local URL. Open the URL on a Chrome/Chromium-based browser. |docs-lint,docs-check-links, anddocs-proofread(ordocs-checkfor all three) catch missing docstrings, broken links, and spelling/grammar issues; see the Command Reference chapter.
💡 Using Claude Code? Type /backend in the Claude Code UI for backend workflow reference (including docs). |
|---|
-
change to the
frontend/directory% cd frontend -
generate frontend types and referral data from the backend (required before type-checking, testing, or building)
% mise run generate-frontend-assets
(this splices the backend's
uvonto the frontend'sPATH; seefrontend/scripts/require-backend-uv.sh. Re-run this any time the backend Pydantic models or referral catalog change.mise run //:setupalso does this, plus a full toolchain provision/install — use that instead only when you need the heavier one-time setup.)This writes
src/types/models.tsfrom the backend Pydantic models andsrc/generated/referrals.tsfrom the validated referral catalog. Both outputs are gitignored. Non-generated frontend types are stored insrc/shared/types/and are checked into source control.
-
run individual checks
- lint TypeScript code with
eslint% mise run lint
- typecheck TypeScript code with
tsc% mise run typecheck
- test TypeScript code with
vitest% mise run test
Each accepts extra flags after
--, e.g.mise run lint -- --fix, the same as the backend checks above — and unlikemise exec, keepsnpm install(via theinstalltask's dependency) up to date if the lockfile changed. - lint TypeScript code with
-
or run the above checks in one-shot (also regenerates frontend assets first)
% mise run check
(equivalent to
mise run //frontend:checkfrom the repo root; see the backend section above for running both together)
💡 Using Claude Code? Type /backend or /frontend in the Claude Code UI for Docker target reference, or /onboarding for the compose quick start. |
|---|
The mise-way to spin up a local deployment of the app in containers (builds the runtime/local targets below for you, then starts and wires up both services) is:
% mise run //:dev --containerThe //: prefix is load-bearing: it names the root task explicitly, so this works from anywhere in the repo. A bare mise run dev from inside frontend/ (where the previous section left you) resolves to //frontend:dev — the Vite dev server, which takes no --container flag.
This is engine-agnostic (Docker, Podman, or apple/container — auto-detected; override with --engine) and is a cross-engine stand-in for docker compose.
Separately, every backend and frontend check task accepts --container (plus --engine) to run that step against the ci target rather than your host tooling — useful for reproducing a CI failure locally, not for running the app itself. It's still somewhat experimental; three things to know:
- It doesn't save you from setting up the repo.
--containerchanges where the check itself runs, but your local install still happens first — expect annpm install(and, for anything that regenerates types, a backend venv sync) before the container starts. So it's a way to reproduce CI, not a way to skipmise run //:setupor avoid installing Node and uv. Making it fully self-contained is possible and partly built, but isn't wired up yet. - Lint and type errors will match CI even when your local packages don't. For
mise run lint --containerandmise run typecheck --container, the eslint, TypeScript and plugin versions come from the image rather than yournode_modules. If CI reports an error you can't reproduce — or your editor is happy but the build isn't — this is the thing to reach for. --containerdoesn't cascade. It applies only to the command you type, not to any step that command triggers. Add it to each thing you want containerized.
The backend's test and check tasks take a second, narrower reproduce-CI flag, --no-env. Where --container changes where a check runs, --no-env changes what environment it sees: it ignores your backend/.env and substitutes the placeholder values .github/workflows/pr-check.yml uses, including a GOOGLE_APPLICATION_CREDENTIALS path that deliberately does not exist. That matters because a developer's .env points at real credentials, so a test that forgets to mock a credential load passes on your machine and fails only in CI. Reach for it before pushing if a test touches configuration or Google Cloud:
% mise run //backend:check --no-envThe two flags can't be combined — the container lane bind-mounts backend/.env, which is the file --no-env exists to hide.
The project has separate Dockerfiles for backend and frontend, each with multiple build stages, if you need to build an image directly. Use --target to pick a stage:
# backend runtime (serves API)
docker build -f backend/Dockerfile --target runtime -t tenantfirstaid-backend:runtime backend
# frontend local/dev server
docker build -f frontend/Dockerfile --target local -t tenantfirstaid-frontend:local .
# frontend production (serves built static app)
docker build -f frontend/Dockerfile --target production -t tenantfirstaid-frontend:production .Copy the root-level env file before running compose:
cp .env.example .envGCP_CREDENTIALS_FILE in this file is a host path to your GCP credentials JSON (the same file referenced by GOOGLE_APPLICATION_CREDENTIALS in backend/.env). Compose bind-mounts it into the container — it is not injected as an app environment variable.
Then start both services:
docker compose up --buildBy default, compose uses:
- backend target:
runtime - frontend target:
local
Override targets at runtime:
RUNTIME_TARGET=ci FRONTEND_TARGET=ci docker compose up --buildStop services:
docker compose downWe currently have regular project meetups: https://www.meetup.com/codepdx/ . Also check out https://www.codepdx.org/ to find our Discord server.
For information on how the application is deployed, where it runs, how to debug issues, and who has access, see Deployment.md.