Skip to content

Repository files navigation

Tenant First Aid

A chatbot that provides legal information related to housing and eviction in Oregon.

Live at https://tenantfirstaid.com/

Local Development

PR Checks CI-CD (Production)

💡 Using Claude Code? Type /onboarding in the Claude Code UI for guided setup assistance.

Prerequisites

GitHub account
  • You will need a GitHub account (free) to contribute to the project. No account is necessary to browse the source code.
mise
  • This repo is a mise monorepo. mise provisions and pins the rest of the toolchain for you — uv (backend Python deps/tools) and node/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 tenantfirstaid Google 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. gcloud is pinned as a per-task tool in the root mise.toml, so it's provisioned on first use — no separate install:
    1. mise run //:gcloud-login (root-qualified) — runs gcloud auth application-default login + set-quota-project, then prints the resulting application default credentials file path
    2. add the printed path as GOOGLE_APPLICATION_CREDENTIALS=<PATH_TO_CREDS> to your backend/.env file
LangChain/LangSmith
  • langsmith Developer (free) or Plus account and API key

Quick Start

  1. clone repo
  2. copy backend/.env.example to a new file named .env in the same directory.
    1. set GOOGLE_APPLICATION_CREDENTIALS as per Google Cloud application default credentials file (requires the project admin to have already granted your Google account access, per that section)
    2. set LANGSMITH_API_KEY as per LangChain/LangSmith
    3. set VERTEX_AI_DATASTORE_LAWS as per the production example in backend/.env.example:20
  3. 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 trust if prompted
  4. (optional) smoke-test your Google Cloud credentials before starting the app: mise run //:gcloud-login-check — it loads GOOGLE_APPLICATION_CREDENTIALS from backend/.env the same way the app does and queries the same Vertex AI Search serving config
  5. mise run dev (starts the backend API and frontend dev server together)
    • or in two separate terminals: mise run //backend:serve and mise run //frontend:dev
  6. Go to http://localhost:5173
  7. Start chatting
💡 Using Claude Code? Type /backend in the Claude Code UI for backend workflow reference.

Backend Development & Checks

  1. change to the backend/ directory
    % cd backend
  • run individual checks

    1. format Python code with ruff

      % mise run fmt
    2. lint Python code with ruff

      % mise run lint
    3. 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

      1. typecheck Python code with mypy
        % mise run typecheck --checker mypy
      2. typecheck Python code with pyrefly
        % mise run typecheck --checker pyrefly
    4. test Python code with pytest

      % mise run test

    To pass extra flags straight through to the underlying tool, lint, typecheck, and test all accept trailing args after --, e.g.

    % mise run lint -- --fix
    % mise run test -- -k test_some_name -v

    This is preferred over mise exec -- uv run <tool> ..., which bypasses the sync dependency and can silently run against a stale .venv after a dependency bump.

  • or run the above checks in one-shot

    % mise run check

    (equivalent to mise run //backend:check from the repo root). check runs lint, typecheck, and test concurrently (after fmt), 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 check from 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-serve will open Safari but can't access the local URL. Open the URL on a Chrome/Chromium-based browser. |

    docs-lint, docs-check-links, and docs-proofread (or docs-check for 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).

Frontend Development & Checks

  1. change to the frontend/ directory

    % cd frontend
  2. 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 uv onto the frontend's PATH; see frontend/scripts/require-backend-uv.sh. Re-run this any time the backend Pydantic models or referral catalog change. mise run //:setup also 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.ts from the backend Pydantic models and src/generated/referrals.ts from the validated referral catalog. Both outputs are gitignored. Non-generated frontend types are stored in src/shared/types/ and are checked into source control.

  • run individual checks

    1. lint TypeScript code with eslint
      % mise run lint
    2. typecheck TypeScript code with tsc
      % mise run typecheck
    3. 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 unlike mise exec, keeps npm install (via the install task's dependency) up to date if the lockfile changed.

  • or run the above checks in one-shot (also regenerates frontend assets first)

    % mise run check

    (equivalent to mise run //frontend:check from 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.

Docker

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 --container

The //: 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. --container changes where the check itself runs, but your local install still happens first — expect an npm 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 skip mise run //:setup or 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 --container and mise run typecheck --container, the eslint, TypeScript and plugin versions come from the image rather than your node_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.
  • --container doesn'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-env

The 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 .

Docker Compose (quick start)

Copy the root-level env file before running compose:

cp .env.example .env

GCP_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 --build

By default, compose uses:

  • backend target: runtime
  • frontend target: local

Override targets at runtime:

RUNTIME_TARGET=ci FRONTEND_TARGET=ci docker compose up --build

Stop services:

docker compose down

Contributing

We currently have regular project meetups: https://www.meetup.com/codepdx/ . Also check out https://www.codepdx.org/ to find our Discord server.

Deployment

For information on how the application is deployed, where it runs, how to debug issues, and who has access, see Deployment.md.

About

A chatbot that provides legal information related to housing and eviction

Resources

Stars

14 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages