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/in production. - 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 ci-ready |
CI parity gate: make ready + make openapi-verify + frontend e2e smoke. |
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. |
Development routing defaults:
http://127.0.0.1:4000is API-only in development (/api/v1metadata and API endpoints).http://127.0.0.1:4001is the canonical frontend SPA entrypoint in development.- Vite keeps proxying
/apiand/rss.xslto:4000so frontend code can use same-origin-style paths.
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: Keep generated client artifacts in
frontend/src/api/generatedaligned withpublic/openapi.yaml.
Always verify the contract before committing API changes.
Always run this before pushing or committing:
make readyFor frontend changes and API contract/OpenAPI changes, run the CI-parity gate:
make ci-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. |
- Official releases run only after the
ciGitHub Actions workflow completes successfully for a commit onmain. - Manual
releaseworkflow dispatch is an emergency/manual replay path and is restricted tomain. - Docker publish uses the exact CI-validated commit SHA for release metadata, OCI labels, and
BUILD_TAG/GIT_SHAwiring. - Branch protection on
mainmust continue to require theciworkflow even though the release workflow also gates on successful CI.
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.
When SENTRY_DSN is present, Sentry is enabled. BUILD_TAG and GIT_SHA become the release identifier, and
RACK_ENV becomes the environment tag.
Triage starts with the newest feed.create, feed.render, and request.error events. Confirm the release tag,
route group, strategy, and outcome before deciding whether the failure is retryable, terminal, or user-facing.
Alert on sustained production request.error spikes or repeated feed.render failures, then tune thresholds from
real incidents.
- 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/.