Skip to content

feat!: rewrite ruby api and frontend with stable-url autosourced feeds#775

Closed
gildesmarais wants to merge 102 commits intomasterfrom
feat/revamp-frontend
Closed

feat!: rewrite ruby api and frontend with stable-url autosourced feeds#775
gildesmarais wants to merge 102 commits intomasterfrom
feat/revamp-frontend

Conversation

@gildesmarais
Copy link
Member

@gildesmarais gildesmarais commented Sep 9, 2025

No description provided.

@gildesmarais gildesmarais added the WIP work in progress label Sep 9, 2025
@gildesmarais gildesmarais moved this to In progress in Project Board Sep 18, 2025
@gildesmarais gildesmarais force-pushed the feat/revamp-frontend branch 2 times, most recently from 3cd0291 to 26085bf Compare September 27, 2025 14:50
gildesmarais and others added 11 commits March 1, 2026 11:40
Reduce misleading accent usage in the result view so non-interactive labels read as informational, not clickable.

Align action grammar by left-aligning the reset CTA and tightening vertical spacing around result metadata for a denser, more consistent layout.

Expose friendlier strategy display names from the API (Standard (recommended), JavaScript pages) so frontend selectors communicate intent instead of implementation details.

Includes matching test/mock updates to preserve contract and UI behavior guarantees.
…tions

Anchor architecture delivery to the current code reality so execution can proceed autonomously with low migration risk.

The plan now explicitly validates assumptions against existing Roda routes, config/auth/feed boundaries, and current OpenAPI/frontend tooling before proposing changes.

Sequence decisions prioritize reversible slices: typed config and observability before boundary contract migration, with async refresh intentionally last due to operational risk.

Outlier handling is documented via commit packaging guidance so cross-cutting work remains explicit and rollback boundaries stay clear.
Introduce a browser-level smoke check so delivery can be self-verified beyond unit/contract tests.

Given Alpine-based dev containers, Playwright now targets the container-native Chromium path for reliable headless execution instead of relying on incompatible downloaded browser binaries.

Keep scope intentionally narrow: one smoke scenario that validates onboarding/auth mode transitions, with Vitest explicitly excluding e2e specs to avoid framework cross-collection.

This provides an immediate autonomous verification path while preserving existing test semantics and pre-commit gates.
Establish explicit architectural decisions before implementation so autonomous delivery has clear guardrails and rollback boundaries.

The ADR set locks migration policy for Data contracts, request-context observability, typed config snapshots, OpenAPI-generated frontend client workflow, and async feed refresh strategy.

This keeps later implementation commits focused on execution rather than design churn.
…able feed runtime

Execute the backend architecture phases by introducing immutable boundary contracts and typed config snapshots while keeping compatibility adapters for existing hash consumers.

Request context is now initialized at middleware entry and propagated into security/error observability so auth, token usage, and failures correlate by request metadata.

A cache-first feed runtime with feature-flagged async refresh is introduced behind safe defaults, with extraction into dedicated handler modules to keep the Roda app surface maintainable.

Outlier note: this is intentionally cross-cutting because these runtime contracts interlock; rollback remains bounded via adapters and ASYNC_FEED_REFRESH_ENABLED=false.
Replace handwritten API wiring with generated OpenAPI client usage so frontend behavior tracks backend contract changes by construction.

Add generation and verification commands plus Makefile integration to fail fast on spec/client drift instead of relying on manual sync.

Hook and test updates preserve existing UX behavior while adapting error handling to generated client semantics.
Establishes phase-gated governance artifacts for AppContext, feature flags, observability events, and API contract policy.

Locks a code-backed assumptions baseline in the delivery plan so implementation decisions stay grounded in current behavior.

Decision: preserve doc-first phase gates while keeping lightweight pre-work inventory in the flags model.
Outlier: combines AppContext and Flags foundations because boot wiring in app.rb now depends on both to remain runnable and testable in each commit.

Decision: make dependency resolution explicit via AppContext and remove direct feature-flag ENV reads from runtime paths in favor of a typed registry.

Adds fail-fast flag validation boundaries and a focused flag-spec suite to keep startup behavior deterministic.
Adds explicit event emission for auth, feed creation, feed rendering, and error handling using the documented schema fields.

Decision: keep existing security logs intact and layer observability events additively to avoid operational blind spots during transition.

Failure events are emitted at boundary points where outcome is final to reduce duplicate noise.
Extends contract enforcement beyond spec generation drift to include OpenAPI linting and frontend generated-client drift verification.

Decision: keep policy checks in CI so contract integrity is enforced consistently across contributors and environments.
- Drop surface card from guest panel — all panels now share `workspace` open layout
- Replace radio cards with a plain <select> for demo source (consistent with Strategy selector)
- Align button vocabulary: btn--ghost for demo run, btn--accent for primary auth/convert actions
- "Back to demo" demoted to btn--meta (gray housekeeping, not invitation)
- Remove ~95 lines of dead CSS: surface*, onboarding-*, radio-card/list*, fieldset/legend, form--spacious, auth-form-actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gildesmarais gildesmarais changed the title feat(frontend): unify and simplify conversion UX feat!: rewrite ruby api and frontend with stable-url autosourced feeds Mar 1, 2026
gildesmarais and others added 9 commits March 1, 2026 17:44
- useStrategies: init isLoading as !!token to prevent empty-select flash on first render
- Strategy select always rendered (disabled + Loading… placeholder while fetching), no layout jump
- Improved hint copy: "Direct fetch — works for most sites. Fast and safe." / "Headless browser — use for JavaScript-rendered pages (React, Angular, SPAs)."

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: lock feed pipeline behavior

* feat!: add namespaced feed pipeline primitives

* feat!: route feeds through shared synchronous service

* refactor!: remove legacy feed-serving path

* refactor!: finalize architecture cleanup

* refactor!: remove dead feed helper

* refactor!: normalize feed responses and stabilize openapi verification

* style: relax rubocop

* chore: update bundler deps

* fix: harden feed loading and negotiated openapi docs

* chore: refresh generated api types
* refactor: replace Astro frontend with Vite and Preact

* fix: simplify result actions and surface feed errors

* fix: accept escaped public feed tokens

* fix: label the result feed url field

* refactor: redesign frontend ui system

* style: refine frontend typography and visual noise

* chore: drop accidental typecheck gate changes

* chore: add frontend typecheck quick-check

* ci: add frontend guardrail checks

* refactor: harden demo contract and simplify ui states

* chore: remove dead frontend components

* style: prettier

* chore: refresh generated openapi client

* chore: update npm deps

* Refactor feed creation UX flow

* Tighten feed result surface

* Refine create flow support surfaces

* Refine feed creation workspace UX

* Simplify feed creation page layout

* Align result screen with simplified create flow

* Refine feed preview and utility surfaces

* Refine token prompt interaction flow

* Polish feed result presentation

* Update frontend contract test for new flow

* Expose json feed URLs and tighten recovery flow
This pull request introduces a major refactor of the backend code
structure to adopt the Zeitwerk autoloader, improves backend
maintainability, and updates development and linting workflows. It
removes manual requires and directory-specific namespace wiring,
reorganizes files under the `app/web/**` directory, and ensures that all
code is autoloaded in a consistent, Rails-like fashion. Additionally, it
adds a Zeitwerk eager-load check to the linting process and updates
documentation accordingly. Several files are renamed or removed to
reflect the new structure, and some error-handling and validation logic
is refactored for clarity and consistency.

**Backend autoloading and structure improvements:**

- Introduced the `zeitwerk` gem and removed the `rack-unreloader` gem,
enabling Zeitwerk-based autoloading for backend code under the
`Html2rss::Web` namespace, and updated `app.rb` to use
`Html2rss::Web::Boot` for setup and eager loading.
[[1]](diffhunk://#diff-d09ea66f8227784ff4393d88a19836f321c915ae10031d16c93d67e6283ab55fR20-L25)
[[2]](diffhunk://#diff-f965f92b425fb2f75d38b491b2625fe21b8af20b7666217546bce8a42b198ea4L9-R12)
- Moved backend files into the `app/web/**` directory to match
Zeitwerk's expectations, and removed manual `require` statements in
favor of autoloading.
[[1]](diffhunk://#diff-f965f92b425fb2f75d38b491b2625fe21b8af20b7666217546bce8a42b198ea4L9-R12)
[[2]](diffhunk://#diff-17e1b7bf4af1869b4be3173dde8b6bffaff1e1e2a6e767b728c9a9f3685d490fL1-L52)
[[3]](diffhunk://#diff-02e7c9c52be0b12bd7c0a8910a098e50de253b9654172526667dec2bfdba4970L1-L57)
[[4]](diffhunk://#diff-884604050285643eb6fb29195c6342cd73a3abae7f3acba5b1bbc41a196d31e3L1-L70)
[[5]](diffhunk://#diff-9df4f6ed17ac1a74c68891a27d537de8e617e0b99e7803fbf42ab523a7043bb6L1-L158)
[[6]](diffhunk://#diff-f2b0bbe2a08fb1204f3985f426ee5aeda21d24f8d4b598e4162b43d87649b674L3-R11)
[[7]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281L6-R15)

**Development and linting workflow updates:**

- Added a Zeitwerk eager-load verification task (`rake zeitwerk:verify`)
to the `Makefile` linting process and as part of `make ready`, ensuring
loader drift is caught early in development.
[[1]](diffhunk://#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52R61-R62)
[[2]](diffhunk://#diff-ee98e028c59b193d58fde56ab4daf54d43c486ae674e63d50ddf300b07943e0fR142-R152)
[[3]](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R69-R72)

**Error handling and validation refactoring:**

- Updated error handling in API modules to reference error classes via
the fully qualified `Html2rss::Web` namespace, and refactored validation
logic in `CreateFeed` for clarity and maintainability.
[[1]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281L44-R43)
[[2]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281L60-R80)
[[3]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281R90-R95)
[[4]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281L112-R123)
[[5]](diffhunk://#diff-0764627d99177e329667ae8f5c98c756ac3d8ae43bdf768b3af26403f713e281L134-R141)

**Roda plugin and middleware changes:**

- Added the `:head` and `:not_allowed` plugins to Roda configuration,
improving HTTP method handling.

**Documentation updates:**

- Updated `README.md` to explain the new backend file structure,
Zeitwerk integration, and contributor guidelines for backend code
placement.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 186 out of 211 changed files in this pull request and generated 9 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +15 to +39
environment:
RACK_ENV: production
PORT: 4000
HTML2RSS_SECRET_KEY: your-generated-secret-key-here
HEALTH_CHECK_TOKEN: CHANGE_ME_HEALTH_CHECK_TOKEN
BROWSERLESS_IO_WEBSOCKET_URL: ws://browserless:4002
BROWSERLESS_IO_API_TOKEN: 6R0W53R135510

watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- "~/.docker/config.json:/config.json"
command: --cleanup --interval 7200

browserless:
image: "ghcr.io/browserless/chromium"
restart: unless-stopped
ports:
- "127.0.0.1:4002:4002"
environment:
PORT: 4002
CONCURRENT: 10
TOKEN: 6R0W53R135510
Comment on lines +17 to +25
def call(router)
router.on 'api', 'v1' do
RequestTarget.mark!(router, RequestTarget::API)
router.response['Content-Type'] = 'application/json'

HealthRoutes.call(router)
FeedRoutes.call(router)
MetadataRoutes.call(router)
end
Comment on lines +44 to +46
{
status: 200,
headers: { 'Content-Type': 'application/json' },
Comment on lines +18 to +25
return HttpResponse.json(
buildFeedResponse({
url: body.url,
feed_token: 'generated-token',
public_url: '/api/v1/feeds/generated-token',
json_public_url: '/api/v1/feeds/generated-token.json',
})
);
Comment on lines +66 to +70
http.post('/api/v1/feeds', async () =>
HttpResponse.text('not-json', {
status: 200,
headers: { 'content-type': 'application/json' },
})
Comment on lines +88 to +98
def validate_account_configuration!
accounts = AccountManager.accounts
weak_tokens = accounts.select { |acc| acc[:token].length < 16 }
return unless weak_tokens.any?

weak_usernames = weak_tokens.map { |acc| acc[:username] }.join(', ')
SecurityLogger.log_config_validation_failure('account_tokens', "Weak tokens for users: #{weak_usernames}")
puts '❌ CRITICAL: Weak authentication tokens detected in production!'
puts ' All tokens must be at least 16 characters long.'
puts " Weak tokens found for users: #{weak_usernames}"
exit 1
Comment on lines +13 to +17
def call(router)
mount_openapi_spec(router)
mount_strategies(router)
mount_root(router)
end
Comment on lines +13 to +23
def call(router, index_renderer:)
router.get String do |feed_name|
next if feed_name.include?('.') && !feed_name.end_with?('.json', '.xml', '.rss')

RequestTarget.mark!(router, RequestTarget::FEED)
Feeds::Responder.call(request: router, target_kind: :static, identifier: feed_name)
end

router.root do
index_renderer.call(router)
end
gildesmarais and others added 3 commits March 15, 2026 10:59
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@gildesmarais
Copy link
Member Author

Rebuild git history in dedicated branch. #879

@github-project-automation github-project-automation bot moved this from In progress to Done in Project Board Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

WIP work in progress

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants