diff --git a/AGENTS.md b/AGENTS.md index be3a879..3482ad6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,6 +40,9 @@ make locust-headless # Locust in headless CLI mode (for CI) | `/messages` | `routers/messages.py` | | `/notifications` | `routers/notifications.py` | | `/votes` | `routers/votes.py` | +| `/reactions` | `routers/reactions.py` | +| `/bookmarks` | `routers/bookmarks.py` | +| `/polls` | `routers/polls.py` | | `/avatar` | `routers/avatar.py` | | `/follow` | `routers/follow.py` | | `/leaderboard` | `routers/leaderboard.py` | @@ -210,7 +213,7 @@ if "comments" not in db.tables: - No comments/docstrings in source - code is self-documenting. - Forbidden variable name patterns: `_new`, `_old`, `_temp`, `_v2`, `better_`, `my_`, `the_` (see CLAUDE.md for full list). - Form validation uses Pydantic models in `models.py` via `Annotated[Model, Form()]` params; invalid input is caught by the global `RequestValidationError` handler in `main.py` (auth pages re-render with messages at 400, other routes redirect). -- Template globals: `get_unread_count(user_uid)`, `get_user_projects(user_uid)`, `avatar_url(style, seed, size)`, `format_date(dt_str, include_time=False)` (ISO → `DD/MM/YYYY` or `DD/MM/YYYY HH:MM`). +- Template globals: `get_unread_count(user_uid)`, `get_unread_messages(user_uid)`, `get_user_projects(user_uid)`, `avatar_url(style, seed, size)`, `format_date(dt_str, include_time=False)` (ISO → `DD/MM/YYYY` or `DD/MM/YYYY HH:MM`). - `DEVPLACE_DATABASE_URL` env var overrides the SQLite path (used by tests). - All `RedirectResponse` must use `status_code=302` (integer, not `status` module). - All `dataset` operations are synchronous and run in the async event loop - keep them fast. No external HTTP calls in request handlers. @@ -291,6 +294,13 @@ page.locator("button:has-text('Delete')") - **`bob` fixture logs in bob_test** in a separate Playwright context. - **Use `alice` for single-user tests.** It returns `(page, user_dict)`. +### Global `site_settings` tests + +- **The uvicorn server is shared for the whole session.** A test that flips a global setting (maintenance mode, registration, rate limit, session length) changes behavior for every later test, so **restore the value in a `try/finally`** — see `tests/test_operational.py`. +- **Saving through the admin form is the reliable mutation path**, not a direct DB write: the form handler runs in the server process and calls `clear_settings_cache()`, so the change takes effect immediately (a direct DB write waits out the 60s server-side cache). +- **`conftest.py`'s `app_server` seeds `rate_limit_per_minute="1000000"`** so the suite is never throttled and the settings form round-trips a safe value instead of the template's `"60"` default. +- **Rate-limit unit tests patch `m.get_int_setting`**, not the `RATE_LIMIT` constant — the constant is now only the fallback default passed to `get_int_setting`. + ### Failure handling - **Failure screenshots auto-save** to `/tmp/devplace_test_screenshots/`. @@ -324,7 +334,11 @@ page.locator("button:has-text('Delete')") ## Notification System -Notifications are created server-side in the route handlers and stored in the `notifications` table. The unread count is cached per-process in `_unread_cache`. +Notifications are created server-side in the route handlers and stored in the `notifications` table. Unread counts are cached per-process in `_unread_cache` (notifications) and `_messages_cache` (messages), both 10s TTL in `templating.py`. Mutating routes call `clear_unread_cache(uid)` / `clear_messages_cache(uid)` to invalidate. + +### Live counter badges + +The top-nav bell and Messages link carry `data-counter="notifications"` / `data-counter="messages"` with a child `