chore: remove stale agent reports and add PYTHONDONTWRITEBYTECODE export to Makefile
Delete four stale SEO and style agent report JSON/MD files from agents/reports/ that had zero findings or were superseded. Export PYTHONDONTWRITEBYTECODE=1 in the Makefile so all make targets suppress bytecode generation, and add a `tree` target that lists the repository file tree via git ls-tree. Update AGENTS.md and CLAUDE.md documentation to reflect that routers now form a directory tree mirroring URL paths and that bytecode writing is disabled project-wide.
This commit is contained in:
@@ -19,6 +19,8 @@ make locust # Locust load test, interactive web UI
|
||||
make locust-headless # Locust CLI mode for CI
|
||||
```
|
||||
|
||||
The Makefile exports `PYTHONDONTWRITEBYTECODE=1` for every recipe, so no `.pyc` files or `__pycache__` directories are written by any `make` target (tests, dev, coverage, agents, locust). Keep it that way - do not add a target that re-enables bytecode writing. `make clean` removes any stray bytecode left from running Python outside make.
|
||||
|
||||
Validate code without running the suite: confirm `python -c "from devplacepy.main import app"` imports clean and check each touched language manually (Python compiles/imports, JS parses, CSS braces and HTML tags balance).
|
||||
|
||||
Single test: `python -m pytest tests/e2e/feed.py::test_name -v --tb=line -x`
|
||||
@@ -66,25 +68,25 @@ Tests run on port 10501 with a tempfile SQLite DB and a dedicated `DEVPLACE_DATA
|
||||
`GET /` is the home page and never redirects: guests get the marketing splash, authenticated users get a personalized home (welcome card with their avatar/stats, a feed shortcut, quick links), both sharing the Latest Posts + Developer News sections. It pulls up to 6 articles where `show_on_landing=1` from the `news` table plus 6 recent posts (joined via the batch helpers in `database.py`). The Latest Posts section and the main `/feed` enforce **author diversity** (at most two posts per author) via `diversify_by_author` / `paginate_diverse` in `database.py` - `paginate_diverse` mirrors `paginate` (same `(rows, next_cursor)` contract) and enforces the cap per page.
|
||||
|
||||
### Routing layout
|
||||
One file per domain in `devplacepy/routers/`. Prefixes are wired in `main.py`:
|
||||
Routers in `devplacepy/routers/` are organised as a **directory tree that mirrors the endpoint (URL) path**, exactly like `tests/`. A domain with a single resource stays one flat file (`feed.py`, `posts.py`, `gists.py`, ...); a domain with several sub-resources is a **package directory** split **one file per sub-resource** (a distinct noun under the domain), never one file per individual endpoint. Each leaf module declares its own `router = APIRouter()` keeping the exact path strings, and the package `__init__.py` aggregates them with `router.include_router(...)`. Because a package exposes `.router` like a module, `main.py` mounts each domain at its prefix unchanged. **Aggregation rules:** a leaf that owns the domain's collection-root (`""`) route must be the package's base router (FastAPI rejects an empty path included under an empty prefix), so the package `__init__` imports that leaf's `router` and includes the rest onto it (see `routers/bugs`, `routers/admin`, `routers/projects`); leaves carved out of a former monolith keep their relative path strings and are included with no sub-prefix, while a router folded in from a deeper mount keeps its own paths and is included with a sub-prefix (`admin/__init__` includes `services.router` with `prefix="/services"`). Module-private helpers shared across a package's leaves live in its `_shared.py`. Prefixes are wired in `main.py`:
|
||||
|
||||
| Prefix | Router |
|
||||
|--------|--------|
|
||||
| `/auth` | auth.py |
|
||||
| `/auth` | auth/ package - one leaf per flow (`signup`, `login`, `logout`, `forgotpassword`, `resetpassword`) |
|
||||
| `/feed` | feed.py |
|
||||
| `/posts` | posts.py |
|
||||
| `/comments` | comments.py |
|
||||
| `/projects` | projects.py - listing/detail/create/delete plus owner-only visibility toggles `POST /{slug}/private` and `POST /{slug}/readonly`, and async `POST /{slug}/fork` (enqueues a fork job) |
|
||||
| `/projects/{slug}/files` | project_files.py - per-project virtual filesystem (CRUD dirs/files, upload, inline edit, plus line-range ops: `lines` read, `replace-lines`, `insert-lines`, `delete-lines`, `append` for large text files) |
|
||||
| `/profile` | profile.py |
|
||||
| `/projects` | projects/ package - `index.py` (listing/detail/create/delete plus owner-only visibility toggles `POST /{slug}/private` and `POST /{slug}/readonly`, and async `POST /{slug}/fork`), `files.py`, and the `containers/` subpackage (below). `main.py` mounts the whole `/projects` tree from this one package |
|
||||
| `/projects/{slug}/files` | projects/files.py - per-project virtual filesystem (CRUD dirs/files, upload, inline edit, plus line-range ops: `lines` read, `replace-lines`, `insert-lines`, `delete-lines`, `append` for large text files) |
|
||||
| `/profile` | profile/ package - `index.py` (page/search/followers/update/api-key), `customization.py`, `notifications.py`, `usage.py` (`_ai_quota`, re-exported from the package) |
|
||||
| `/messages` | messages.py |
|
||||
| `/notifications` | notifications.py |
|
||||
| `/votes` | votes.py |
|
||||
| `/avatar` | avatar.py |
|
||||
| `/follow` | follow.py |
|
||||
| `/admin` | admin.py |
|
||||
| `/admin/services` | services.py |
|
||||
| `/bugs` | bugs.py - bug tracker backed by Gitea (no local issue store): list (`?state=`/`?page=`), detail (`/{number}` with comments), async AI-enhanced filing (`/create` enqueues a `bug_create` job, status at `/jobs/{uid}`), `/{number}/comment` (synchronous, pushes to Gitea + notifies admins), `/{number}/status` (admin open/closed) |
|
||||
| `/admin` | admin/ package - one leaf per sub-resource (`index`, `users`, `aiusage`, `aiquota`, `media`, `trash`, `settings`, `notifications`, `news`, `auditlog`) plus the folded-in `services.py` and `containers.py` (mounted with `/services` and `/containers` sub-prefixes). `main.py` mounts the whole `/admin` tree from this one package |
|
||||
| `/admin/services` | admin/services.py |
|
||||
| `/bugs` | bugs/ package - bug tracker backed by Gitea (no local issue store): `index.py` (list `?state=`/`?page=`, detail `/{number}` with comments), `create.py` (async AI-enhanced filing `/create` enqueues a `bug_create` job, status at `/jobs/{uid}`), `comment.py` (synchronous, pushes to Gitea + notifies admins), `status.py` (admin open/closed) |
|
||||
| `/gists` | gists.py |
|
||||
| `/news` | news.py |
|
||||
| `/uploads` | uploads.py |
|
||||
@@ -92,12 +94,12 @@ One file per domain in `devplacepy/routers/`. Prefixes are wired in `main.py`:
|
||||
| `/devii` | devii.py - WebSocket terminal (`/devii/ws`), page, `/devii/usage`, `/devii/session` |
|
||||
| `/zips` | zips.py - generic zip job status (`/zips/{uid}`) and archive download (`/zips/{uid}/download`); enqueued from `/projects/{slug}/zip` and `/projects/{slug}/files/zip` |
|
||||
| `/forks` | forks.py - fork job status (`/forks/{uid}`); enqueued from `/projects/{slug}/fork`. When done its `project_url` points at the new forked project |
|
||||
| `/projects/{slug}/containers` | containers.py - admin per-project container manager: instance creation (modal forms; every instance runs the shared `ppy` image), one-shot + PTY-WS exec, logs/metrics, cron/interval/once schedules. Discoverable from the project detail page (admin-only **Containers** button) and from the admin index |
|
||||
| `/admin/containers` | containers_admin.py - admin **Containers** sidebar section: `/admin/containers` lists every instance across all projects (clickable rows), `/admin/containers/{uid}` is the dedicated per-instance detail page (lifecycle, live logs/metrics, PTY terminal, schedules, ingress, sync). It is a discoverable index + detail VIEW layered over the per-project `/projects/{slug}/containers/instances/{uid}/...` endpoints - the instance carries its `project_uid`, so the detail page resolves the project and the page JS targets those existing routes (no lifecycle/logs/exec backend is duplicated) |
|
||||
| `/projects/{slug}/containers` | projects/containers/ subpackage - admin per-project container manager (`instances.py` for creation/lifecycle/exec/logs/metrics/sync plus the exec websocket, `schedules.py` for cron/interval/once schedules, shared helpers in `_shared.py`). Every instance runs the shared `ppy` image. Discoverable from the project detail page (admin-only **Containers** button) and from the admin index |
|
||||
| `/admin/containers` | admin/containers.py - admin **Containers** sidebar section: `/admin/containers` lists every instance across all projects (clickable rows), `/admin/containers/{uid}` is the dedicated per-instance detail page (lifecycle, live logs/metrics, PTY terminal, schedules, ingress, sync). It is a discoverable index + detail VIEW layered over the per-project `/projects/{slug}/containers/instances/{uid}/...` endpoints - the instance carries its `project_uid`, so the detail page resolves the project and the page JS targets those existing routes (no lifecycle/logs/exec backend is duplicated) |
|
||||
| `/p/{slug}` | proxy.py - public ingress reverse proxy (HTTP + WebSocket) to a running container instance's published host port, opt-in per instance via `ingress_slug` |
|
||||
| (none) | seo.py - `/robots.txt`, `/sitemap.xml` |
|
||||
|
||||
Docs (`routers/docs.py` `DOCS_PAGES`) entries take optional `admin: True` (hidden + 404 for non-admins, but still indexed and surfaced only to admins by `docs_search`) and `section: "..."` (a nested sidebar group rendered by `docs_base.html`). The member-facing `devii` prose page is functional; admins also get a `Devii internals` section of `devii-*` technical subpages. Prose docs pages are rendered to HTML **server-side** (`docs_prose.py`, mistune GFM): the handler converts the `data-render` markdown block to HTML before sending and drops the `data-render` attribute, so the client never re-renders it (no markdown-to-HTML flicker, faster first paint). Authored example markup inside that block is therefore still HTML-escaped (`<dp-...>`); content outside the block (component live-demo blocks and their `<script type="module">`) passes through untouched. User-generated content elsewhere still uses the client `data-render` pipeline. The public `Components` section (`component-*` prose pages) documents the custom web components with a **live, interactive example** on each page. The public `Styles` section (`styles`, `styles-colors`, `styles-layout`, `styles-responsiveness`, `styles-consistency`) is the design-system reference: the colour tokens and their meaning, the approved page layouts, the responsive breakpoint ladder, and the HARD structural rules every page must follow (taken from the feed/posts page as the canonical implementation). It uses the same live-demo convention (real demo markup OUTSIDE the `data-render` block, example markup inside it entity-escaped). A prose page's `<div class="docs-content" data-render>` is rendered client-side via marked + DOMPurify on `textContent`, so any example markup shown as code inside it MUST be HTML-escaped (`<dp-dialog>`) or the browser parses it as a real element before render; the live demo itself goes in a separate block OUTSIDE the `data-render` div, where a `<script type="module">` (which imports its own component module, since it executes before `Application.js`) wires it up.
|
||||
Docs (`routers/docs/pages.py` `DOCS_PAGES`, re-exported from the `routers/docs` package; handlers in `routers/docs/views.py`) entries take optional `admin: True` (hidden + 404 for non-admins, but still indexed and surfaced only to admins by `docs_search`) and `section: "..."` (a nested sidebar group rendered by `docs_base.html`). The member-facing `devii` prose page is functional; admins also get a `Devii internals` section of `devii-*` technical subpages. Prose docs pages are rendered to HTML **server-side** (`docs_prose.py`, mistune GFM): the handler converts the `data-render` markdown block to HTML before sending and drops the `data-render` attribute, so the client never re-renders it (no markdown-to-HTML flicker, faster first paint). Authored example markup inside that block is therefore still HTML-escaped (`<dp-...>`); content outside the block (component live-demo blocks and their `<script type="module">`) passes through untouched. User-generated content elsewhere still uses the client `data-render` pipeline. The public `Components` section (`component-*` prose pages) documents the custom web components with a **live, interactive example** on each page. The public `Styles` section (`styles`, `styles-colors`, `styles-layout`, `styles-responsiveness`, `styles-consistency`) is the design-system reference: the colour tokens and their meaning, the approved page layouts, the responsive breakpoint ladder, and the HARD structural rules every page must follow (taken from the feed/posts page as the canonical implementation). It uses the same live-demo convention (real demo markup OUTSIDE the `data-render` block, example markup inside it entity-escaped). A prose page's `<div class="docs-content" data-render>` is rendered client-side via marked + DOMPurify on `textContent`, so any example markup shown as code inside it MUST be HTML-escaped (`<dp-dialog>`) or the browser parses it as a real element before render; the live demo itself goes in a separate block OUTSIDE the `data-render` div, where a `<script type="module">` (which imports its own component module, since it executes before `Application.js`) wires it up.
|
||||
|
||||
### Templates and frontend
|
||||
- All routers MUST import the shared `templates` from `devplacepy.templating`. Do NOT instantiate `Jinja2Templates` per router - globals (`avatar_url`, `get_unread_count`, `get_user_projects`, `format_date`) are registered on that single instance.
|
||||
|
||||
Reference in New Issue
Block a user