|
| 1 | +# `app/web` Rules For AI Agents |
| 2 | + |
| 3 | +This file is intentionally prescriptive. If you are an AI coding agent changing Ruby backend code, follow these rules before adding files or moving code. |
| 4 | + |
| 5 | +## Namespace Contract |
| 6 | + |
| 7 | +- `app/` is the Zeitwerk root for `Html2rss`. |
| 8 | +- `app/web/**` maps to the `Html2rss::Web` namespace. |
| 9 | +- Do not add `require_relative` calls between files under `app/web/**` unless the file is a non-Zeitwerk boot entrypoint. |
| 10 | +- Path, filename, and constant name must match. If a constant is `Html2rss::Web::SecurityLogger`, the file belongs at `app/web/security/security_logger.rb`. |
| 11 | + |
| 12 | +## Directory Placement |
| 13 | + |
| 14 | +Use the narrowest concern folder that fits the object. |
| 15 | + |
| 16 | +- `app/web/api/`: API contract and endpoint implementation objects. |
| 17 | +- `app/web/boot/`: process boot, loader setup, dev reload, runtime setup. |
| 18 | +- `app/web/config/`: environment flags, local config loading, config snapshots. |
| 19 | +- `app/web/domain/`: backend domain helpers that do not belong to API, request, rendering, or security. |
| 20 | +- `app/web/errors/`: error classes and error response serialization. |
| 21 | +- `app/web/feeds/`: feed fetching, rendering orchestration, cache use, feed service contracts. |
| 22 | +- `app/web/http/`: low-level HTTP response/cache helpers. |
| 23 | +- `app/web/rendering/`: content negotiation and feed output builders. |
| 24 | +- `app/web/request/`: request-scoped context and middleware. |
| 25 | +- `app/web/routes/`: Roda route composition only. |
| 26 | +- `app/web/security/`: auth, token handling, account access, SSRF request strategy, security logging. |
| 27 | +- `app/web/telemetry/`: observability event emission only. |
| 28 | + |
| 29 | +## Placement Heuristics |
| 30 | + |
| 31 | +- Put code in `routes/` only if it mounts or composes Roda request branches. |
| 32 | +- Put code in `api/` only if it is specific to `/api/v1` contracts or endpoint behavior. |
| 33 | +- Put code in `feeds/` if it is part of fetching, resolving, rendering, or caching feeds. |
| 34 | +- Put code in `domain/` only as a last resort. If a better concern folder exists, use it. |
| 35 | +- Do not create generic buckets such as `services`, `utils`, `helpers`, or `concerns`. |
| 36 | + |
| 37 | +## Consolidation Rules |
| 38 | + |
| 39 | +- Prefer concern folders over a flat `app/web/` root. |
| 40 | +- Do not merge unrelated objects just to reduce file count. |
| 41 | +- Consolidate only when one file is clearly a thin wrapper around another concept and the merged object still has a single responsibility. |
| 42 | +- If a file defines multiple top-level constants, stop and check whether Zeitwerk naming or the public API would become less clear. |
| 43 | + |
| 44 | +## Boot And Runtime Rules |
| 45 | + |
| 46 | +- `app.rb` should declare the Roda app and its Rack/Roda plugins. |
| 47 | +- Process-level boot side effects belong in `app/web/boot/**`. |
| 48 | +- Register external runtime integrations, validate environment, and configure shared services in boot objects, not inline in the Roda class body. |
| 49 | + |
| 50 | +## Route Rules |
| 51 | + |
| 52 | +- Keep route composition centralized in `app/web/routes/**`. |
| 53 | +- Split route modules by endpoint concern when a route file grows, but preserve matching order. |
| 54 | +- Root metadata routes must use exact matching (`r.is`) so they do not swallow subpaths. |
| 55 | + |
| 56 | +## Change Checklist |
| 57 | + |
| 58 | +- Update or add specs for the behavior you moved. |
| 59 | +- Run `docker compose -f .devcontainer/docker-compose.yml exec -T app make ready`. |
| 60 | +- Smoke the app at `http://127.0.0.1:4001/` when request or UI behavior changed. |
0 commit comments