From 61c24f7d1e21a573c9e19027baa97d6bb4f3fc45 Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 6 Jun 2026 16:31:42 +0200 Subject: [PATCH] Update --- AGENTS.md | 68 ++++- README.md | 33 +- devplacepy/config.py | 4 +- devplacepy/constants.py | 2 + devplacepy/content.py | 18 ++ devplacepy/database.py | 218 +++++++++++++- devplacepy/main.py | 30 +- devplacepy/models.py | 27 +- devplacepy/push.py | 3 +- devplacepy/routers/auth.py | 23 +- devplacepy/routers/avatar.py | 5 +- devplacepy/routers/bookmarks.py | 87 ++++++ devplacepy/routers/comments.py | 3 +- devplacepy/routers/feed.py | 8 +- devplacepy/routers/messages.py | 4 +- devplacepy/routers/news.py | 4 +- devplacepy/routers/notifications.py | 22 +- devplacepy/routers/polls.py | 44 +++ devplacepy/routers/posts.py | 28 +- devplacepy/routers/profile.py | 18 +- devplacepy/routers/reactions.py | 46 +++ devplacepy/services/news.py | 3 +- devplacepy/static/css/admin.css | 5 - devplacepy/static/css/attachments.css | 10 +- devplacepy/static/css/auth.css | 1 - devplacepy/static/css/base.css | 32 +- devplacepy/static/css/engagement.css | 318 ++++++++++++++++++++ devplacepy/static/css/feed.css | 8 +- devplacepy/static/css/gists.css | 4 - devplacepy/static/css/landing.css | 5 - devplacepy/static/css/mention.css | 1 - devplacepy/static/css/messages.css | 6 +- devplacepy/static/css/news.css | 4 - devplacepy/static/css/notifications.css | 11 +- devplacepy/static/css/post.css | 21 +- devplacepy/static/css/profile.css | 64 +++- devplacepy/static/css/projects.css | 2 - devplacepy/static/css/sidebar.css | 1 - devplacepy/static/js/Application.js | 8 + devplacepy/static/js/AttachmentUploader.js | 14 +- devplacepy/static/js/BookmarkManager.js | 27 ++ devplacepy/static/js/CounterManager.js | 44 +++ devplacepy/static/js/DomUtils.js | 15 + devplacepy/static/js/Http.js | 3 + devplacepy/static/js/ModalManager.js | 2 +- devplacepy/static/js/NotificationManager.js | 6 +- devplacepy/static/js/PollManager.js | 182 +++++++++++ devplacepy/static/js/ReactionBar.js | 65 ++++ devplacepy/templates/_bookmark_button.html | 5 + devplacepy/templates/_comment.html | 9 +- devplacepy/templates/_poll.html | 18 ++ devplacepy/templates/_post_card.html | 10 +- devplacepy/templates/_post_votes.html | 4 +- devplacepy/templates/_reaction_bar.html | 24 ++ devplacepy/templates/admin_settings.html | 64 +++- devplacepy/templates/base.html | 39 +-- devplacepy/templates/bugs.html | 4 +- devplacepy/templates/feed.html | 17 +- devplacepy/templates/gist_detail.html | 4 +- devplacepy/templates/gists.html | 4 +- devplacepy/templates/messages.html | 2 +- devplacepy/templates/news_detail.html | 1 + devplacepy/templates/notifications.html | 10 +- devplacepy/templates/post.html | 8 +- devplacepy/templates/profile.html | 22 +- devplacepy/templates/project_detail.html | 2 + devplacepy/templates/projects.html | 4 +- devplacepy/templates/saved.html | 27 ++ devplacepy/templates/signup.html | 14 + devplacepy/templating.py | 21 +- devplacepy/utils.py | 9 +- tests/conftest.py | 7 + tests/test_bookmarks.py | 95 ++++++ tests/test_engagement_ui.py | 76 +++++ tests/test_operational.py | 140 +++++++++ tests/test_polls.py | 128 ++++++++ tests/test_ratelimit.py | 25 +- tests/test_reactions.py | 155 ++++++++++ tests/test_streaks.py | 111 +++++++ 79 files changed, 2431 insertions(+), 185 deletions(-) create mode 100644 devplacepy/routers/bookmarks.py create mode 100644 devplacepy/routers/polls.py create mode 100644 devplacepy/routers/reactions.py create mode 100644 devplacepy/static/css/engagement.css create mode 100644 devplacepy/static/js/BookmarkManager.js create mode 100644 devplacepy/static/js/CounterManager.js create mode 100644 devplacepy/static/js/PollManager.js create mode 100644 devplacepy/static/js/ReactionBar.js create mode 100644 devplacepy/templates/_bookmark_button.html create mode 100644 devplacepy/templates/_poll.html create mode 100644 devplacepy/templates/_reaction_bar.html create mode 100644 devplacepy/templates/saved.html create mode 100644 tests/test_bookmarks.py create mode 100644 tests/test_engagement_ui.py create mode 100644 tests/test_operational.py create mode 100644 tests/test_polls.py create mode 100644 tests/test_reactions.py create mode 100644 tests/test_streaks.py 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 `