Welcome! This is the canonical source of truth for contributing to html2rss-web.
- Start here for contributors: This document.
- Architecture & Request Lifecycle: docs/architecture.md
- UI/Design rules: docs/design-system.md
- Agent execution constraints: AGENTS.md
- Generated contract artifacts:
public/openapi.yaml - Public-facing intro: README.md
html2rss-web converts arbitrary websites into RSS 2.0 feeds.
- Backend: Ruby + Roda under the
Html2rss::Webnamespace. - Frontend: Preact + Vite, built into
frontend/distand served at/. - Feed extraction: Delegated to the
html2rssgem. - Distribution: Docker Compose / Dev Container first.
- Runtime behavior: Application code plus tests.
- HTTP contract: Request specs plus generated OpenAPI.
- This file: Contributor conventions and current project rules.
Use the repository's Dev Container for all local development and tests. Running the app directly on the host is not supported.
| Command | Purpose |
|---|---|
make setup |
Install Ruby and Node dependencies. |
make dev |
Run Ruby (port 4000) and frontend (port 4001) dev servers. |
make ready |
Pre-commit gate: make quick-check + bundle exec rspec. |
make test |
Run Ruby and frontend test suites. |
make lint |
Run all linters. |
make yard-verify-public-docs |
Enforce typed YARD docs for public methods in app/. |
make openapi |
Regenerate public/openapi.yaml from request specs. |
make openapi-verify |
Verify generated OpenAPI and frontend client artifacts are current. |
make openapi-lint |
Lint OpenAPI with Redocly + Spectral. |
| Command | Purpose |
|---|---|
pnpm run dev |
Vite dev server with hot reload (port 4001). |
pnpm run build |
Build static assets into frontend/dist/. |
pnpm run lint |
Run ESLint across the frontend workspace. |
pnpm run test:run |
Unit tests (Vitest). |
pnpm run test:contract |
Contract tests with MSW. |
To change or add API endpoints, follow this sequence:
- Ruby Request Spec: Define the new behavior or endpoint in
spec/html2rss/web/app_integration_spec.rbor a dedicated request spec. - OpenAPI Generation: Run
make openapiinside the Dev Container to regeneratepublic/openapi.yamlfrom the spec metadata. - Verify Contract: Run
make openapi-verifyandmake openapi-lintto ensure the generated file matches the specs and is valid. - Frontend Client: The frontend generated client in
frontend/src/api/generatedis updated by the build process.
Always verify the contract before committing API changes.
Always run this before pushing or committing:
make ready| Layer | Tooling | Focus |
|---|---|---|
| Ruby API | RSpec + Rack::Test | Feed creation, retrieval, auth paths. |
| Frontend unit | Vitest + Testing Library | Component rendering and hooks with mocked fetch. |
| Frontend contract | Vitest + MSW | End-to-end fetch flows against mocked API responses. |
| Docker smoke | RSpec (:docker) |
Net::HTTP probes against the containerised service. |
app/is the Zeitwerk root forHtml2rss.app/web/**maps directly toHtml2rss::Web::*.- Match constant, filename, and directory exactly.
- Keep route composition in
app/web/routes/**. - Keep
/api/v1contract-specific code inapp/web/api/**. - Keep feed fetching, caching, and orchestration in
app/web/feeds/**. - Keep auth, token handling, URL validation, and security logging in
app/web/security/**. - Keep request-scoped context in
app/web/request/**. - Keep boot/runtime setup in
app/web/boot/**. - Do not create generic buckets such as
services,helpers,utils, orconcerns.
public/openapi.yamlis generated output, not hand-edited design prose.- Backend behavior and request specs define the contract.
- Regenerate with
make openapi. - Drift must fail with
make openapi-verify. - Quality must fail with
make openapi-lint. - Frontend generated client code under
frontend/src/api/generatedis machine-generated only.
Search these pages for examples, plugins, and configuration options:
- Roda: roda.jeremyevans.net
- Preact & Vite: preactjs.com and vite.dev
- html2rss: github.com/html2rss/html2rss
- Testing (Ruby): rspec.info, rubocop.org, betterspecs.org
- URL Handling: Never use Ruby's
URIclass oraddressablegem directly. UseHtml2rss::Urlfor all URL logic. - SSRF Protection: Delegated to the
html2rssgem's built-in security features. Do not bypass these protections or weaken CSP. - Secrets: Never leak stack traces, auth tokens, or internal secrets in HTTP responses.
- Data Protection: Auth tokens provided by users must never be exposed or logged.
- No Persistence: Do not add databases, ORMs, or background job systems.
- Backend Style:
- Keep the main
app.rbthin; organize routes inHtml2rss::Web::Routes::*. - For helpers, use
class << selfandprivatemethods. Avoidmodule_function. - Use YARD doc comments for all public methods in
app/. - Add
# frozen_string_literal: trueto all Ruby files. - Do not use
send(...)to reach into private APIs; expose what is needed at the module level.
- Keep the main
- Frontend Style:
- Follow visual and CSS rules in design-system.md.
- Use Preact components in
frontend/src/. - Use shared styles in
public/shared-ui.cssor app-specific styles infrontend/src/styles/. - Do not modify
frontend/dist/directly.
- Testing:
- Use
ClimateControl.modifyfor tests that change environment variables. - Use
:aggregate_failuresto resolveRSpec/MultipleExpectationswarnings.
- Use
Managed flags and environment keys:
| Name | Env key | Type | Default |
|---|---|---|---|
auto_source_enabled |
AUTO_SOURCE_ENABLED |
boolean | true in development/test, else false |
async_feed_refresh_enabled |
ASYNC_FEED_REFRESH_ENABLED |
boolean | false |
async_feed_refresh_stale_factor |
ASYNC_FEED_REFRESH_STALE_FACTOR |
integer >= 1 |
3 |
health_check_token |
HEALTH_CHECK_TOKEN |
string | nil |
build_tag |
BUILD_TAG |
string | unknown outside production |
git_sha |
GIT_SHA |
string | unknown outside production |
sentry_dsn |
SENTRY_DSN |
string | nil |
sentry_enable_logs |
SENTRY_ENABLE_LOGS |
boolean | false |
Rules:
- Invalid managed flag values must fail fast at boot.
- Unknown managed feature-style env keys must fail fast at boot.
BUILD_TAGandGIT_SHAare required in production so startup logs can identify the deployed build.- Add or change flags in code, tests, and this table together.
Canonical event fields: event_name, schema_version, request_id, route_group, actor, outcome.
Critical-path event families: auth, feed create, feed render, request errors.
- Prefer deleting stale docs over archiving them in-place.
- If a rule matters to contributors, keep it here.
- If a detail is generated from code, keep it out of prose docs.
- If a design idea is temporary, keep it in the PR or issue, not under
docs/.