This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Before making assumptions about how to run commands, deploy, or configure production/hosting:
README.md— Project overview, local development setup, all management commands, tech stackHOSTING.md— Infrastructure, VPS setup, DNS, SSL, environment variables, external servicesDEPLOYMENT.md— CI/CD pipeline, manual deploy, rollback, backups, Dockerfile overview, troubleshooting
Always consult these files first when the task involves deployment, environment configuration, infrastructure, or operational commands.
# Start PostGIS database (required for tests)
docker compose up db -d
# Start app + db + Tailwind watcher for local development
docker compose up app db tailwind
# Build CSS once
npm run build:css
# Run all tests (unit + integration, excludes browser E2E)
nox -s tests
# Run browser E2E tests (Playwright + Chromium)
nox -s e2e
# Note: nox is configured to use uv for package installation
# Run a single test
nox -s tests -- libraries/tests.py::TestLibraryModel::test_create_library_with_all_fields
# Run tests with verbose output
nox -s tests -- -v
# Apply migrations
python manage.py migrate
# Run dev server
python manage.py runserver
# Seed local demo data (reuses images from libraries_examples/ if available)
python manage.py seed_libraries --reset --count 36 --images-dir libraries_examples --seed 42
# Same seed command inside Docker app container
docker compose exec app python manage.py seed_libraries --reset --count 36 --images-dir libraries_examples --seed 42
# Build and preview docs locally
python manage.py export_openapi_schema > docs/openapi.json
zensical serve
# Build docs
zensical buildAfter implementing changes that touch templates, views, JavaScript, HTMX interactions, URL routing, or static assets, run the browser E2E suite before considering the task done:
nox -s e2eThis runs 18 Playwright tests covering homepage (HTMX load, pagination), map page (Leaflet init, view switching, GeoJSON fetch), submit form (autocomplete, geocoding, full submission), library detail (report/photo toggles, HTMX submit), and statistics (Chart.js rendering). External APIs (Photon, Nominatim, OSM tiles) are mocked at the browser level for determinism.
The E2E tests require PostGIS running (docker compose up db -d) and CSS built (npm run build:css). They run in a separate nox session from unit tests and both gate CI deployment.
Tests live in tests/e2e/ with shared fixtures in tests/e2e/conftest.py. When adding new pages or JS interactions, add corresponding E2E test coverage.
After UI/template/static changes, always run this check before considering the task done:
Before restarting the app or running smoke checks after model changes, always apply migrations first.
# Local runtime
python manage.py migrate
# Docker runtime
docker compose exec app python manage.py migrateIf you see errors like column ... does not exist, treat that as a migration mismatch and run migrations before any further debugging.
- Start the full stack and rebuild app image when needed:
docker compose up -d --build app db tailwind
docker compose ps- Verify HTTP responses for homepage and compiled CSS:
curl -I http://localhost:8000/
curl -I http://localhost:8000/static/css/app.cssExpected: both return 200 OK.
- Inspect logs for runtime issues:
docker compose logs --no-color --tail=120 app tailwindExpected:
tailwindcontainer stays running (watch mode active)- no repeated
Not Found: /static/css/app.cssin app logs
- Validate real rendering with Playwright tools:
- Open
http://localhost:8000/ - Capture snapshot/screenshot
- Check browser console for errors
- Check network requests (including static assets)
If the page looks unstyled in browser, treat it as a blocker and debug Docker static file serving before moving on.
If a page suddenly renders without styles, run this checklist before doing anything else:
- Rebuild CSS in the running stack:
docker compose up -d app db tailwind
docker compose exec tailwind npm run build:css- Verify the stylesheet endpoint:
curl -I http://localhost:8000/static/css/app.cssExpected:
200 OK- non-trivial
Content-Length(typically tens of KB, not a tiny fallback file)
-
Hard refresh the browser (
Cmd+Shift+R) to clear cached CSS. -
If still broken, restart and inspect logs:
docker compose restart app tailwind
docker compose logs --no-color --tail=120 app tailwind- Treat repeated
Not Found: /static/css/app.cssor500responses for that path as blockers.
Note: static/css/app.css is generated and gitignored. Regenerate it when needed, but do not commit it.
When any change touches API endpoints (libraries/api.py, libraries/api_schemas.py, libraries/search.py, or URL routing), update the corresponding docs before finishing:
- Update the relevant markdown file under
docs/(e.g.,docs/libraries/list-and-search.md) to reflect new, changed, or removed parameters, endpoints, or response shapes. - Commit the docs markdown changes alongside the code changes.
Note: docs/openapi.json is generated and gitignored. The docs CI workflow regenerates it automatically on every push. Do not commit it.
When any API feature is added, changed, or removed, update docs/changelog.md with a brief entry describing the change. Group entries under a version heading. Commit the changelog update alongside the code changes. Non-API changes (admin features, management commands, website-only features) do not belong in this changelog.
When adding or removing a user-facing feature on the website (views, templates, forms), always ask the user whether the same change should also be reflected in the API. Features that exist in one surface but not the other can cause confusion for consumers. Examples:
- Adding a new filter to a web page listing — ask if the API list endpoint should support the same filter.
- Removing a field from a web form — ask if the corresponding API input schema should also drop the field.
- Adding a new web page for a resource — ask if a matching API endpoint is needed.
Every user-facing string must be translatable. When adding, removing, or updating an English string in templates, views, or forms:
- Wrap the string with
{% trans %}(templates) orgettext/gettext_lazy(Python code). For inline JS strings inside<script>blocks in Django templates, use{% trans %}directly in the string literal. - Add or update the corresponding
msgid/msgstrpair inlocale/it/LC_MESSAGES/django.po. - Run
python manage.py makemessages -l it --no-wrapthenpython manage.py compilemessagesto regenerate the.mofile. - Avoid
%(name)s-style placeholders inside{% trans %}for JS-only variables — use{name}brace placeholders instead (gettext marks%(...)saspython-formatand doubles the%). Use JS.replace("{name}", value)to substitute at runtime.
- Add a docstring to every new function, method, and test function.
- When touching existing code, add missing docstrings in the edited scope before finishing.
- Keep docstrings to exactly two lines: a concise summary sentence and one intent sentence.
- Do not include args/kwargs/returns sections in docstrings.
- Do not tell the user to run cleanup commands that the agent can run directly.
- If branch switching, pulling, or other git operations are blocked by generated local changes, resolve them automatically when safe.
- Treat
static/css/app.cssas a generated asset that is intentionally gitignored: regenerate it withnpm run build:csswhen needed, but do not commit it. - When frontend code is intentionally changed, commit source changes only (
assets/, templates, scripts) and verify styling with a freshnpm run build:cssbefore finishing.
Django 6 project with PostGIS for geospatial data. Two apps:
- users — Custom
AbstractUsermodel (AUTH_USER_MODEL = "users.User"), minimal extension for future flexibility. - libraries — Core domain.
Librarymodel stores little free library locations with a PostGISPointField(SRID 4326).Reportmodel tracks user-submitted issues about libraries. Both use status workflows managed through Django admin actions (approve/reject libraries, resolve/dismiss reports).
Admin uses GISModelAdmin for the Library model to support map-based editing.
URL routing currently includes web pages (/, /login/, /register/, /latest-entries/) plus Django admin (/admin/). API is scaffolded at /api/v1/ and will be expanded.
When adding a new model field or modifying queries, ensure fields used in filter(), order_by(), list_filter, or values_list() lookups have a database index. Add indexes in the model's Meta.indexes list and generate a migration. Skip indexes for low-cardinality boolean fields or fields that are rarely queried.
- Slug generation:
Library.save()auto-generates unique slugs fromcity + address + name, with numeric suffixes for duplicates, truncated to fitmax_length. - Database config: Uses
dj-database-urlto parseDATABASE_URLenv var. GIS library paths (GDAL_LIBRARY_PATH,GEOS_LIBRARY_PATH) are read from environment inconfig/settings.py. - Test fixtures: Shared fixtures (
user,admin_user,admin_client) in rootconftest.py. App-specific fixtures (library,admin_library,admin_report) inlibraries/tests.py. E2E fixtures (e2e_user,approved_libraries,single_library,mock_external_apis,authenticated_page) intests/e2e/conftest.py. - Environment:
.envrc(direnv) setsDATABASE_URL,GDAL_LIBRARY_PATH, andGEOS_LIBRARY_PATHfor local macOS development..env.examplehas Docker equivalents. - Seed data command:
seed_librariescan reset and generate realistic sampleLibraryrows with geospatial points and status mix. It accepts--reset,--count,--seed, and--images-dirand will reuse local images when provided. - Local seed images:
libraries_examples/is intentionally gitignored so each developer can use their own local photo set for seeding.
Production logs are shipped to Grafana Cloud Loki via Dokku Vector.
- Use
app="book-corners"as the default stream selector. - In Grafana Explore, use datasource
grafanacloud-andreagrandi-logs(type: Loki). - Legacy examples using
source="book-corners"may still return older entries, but new queries should useapp.
- Open Explore.
- Select datasource
grafanacloud-andreagrandi-logs. - Keep Builder mode.
- Add label filter
app = book-corners. - Set time range to Last 24 hours.
- Click Run query.
To make this one-click in future, save the query as All logs (book-corners) from Saved queries.
# Recent app logs
logcli query '{app="book-corners"}' --since 24h --limit 50
# Errors only
logcli query '{app="book-corners"} | json | level="error"' --since 24h --limit 50
# Search for specific text
logcli query '{app="book-corners"} |= "search term"' --since 24h --limit 50
# Tail logs live
logcli query '{app="book-corners"}' --tail
# Ignore common bot noise
logcli query '{app="book-corners"} != "wp-admin/setup-config.php" != "wordpress/wp-admin/setup-config.php"' --since 24h --limit 50Requires LOKI_ADDR, LOKI_USERNAME, and LOKI_PASSWORD environment variables (see HOSTING.md for setup).
# Ensure Vector picked up the sink and is healthy
sudo dokku logs:report book-corners
sudo dokku logs:vector-logs 2>&1 | tail -80If logs stop after reconfiguration, rerun scripts/setup_loki.sh from HOSTING.md. The script validates token auth with a direct Loki push (expects HTTP 204) before applying Dokku config.
Python 3.14. PostGIS 17. Key packages: django, psycopg2-binary, Pillow, dj-database-url, pytest, pytest-django, gunicorn, structlog.
ALWAYS use uv to install packages but do not use its lock system. Use simple requirements files instead.