Skip to content
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
850e742
Align result and feed UI
gildesmarais Mar 29, 2026
94940ab
chore: install eslint and style-migrate
gildesmarais Mar 31, 2026
a810257
Add stylelint config and harden feed reader fallback
gildesmarais Apr 3, 2026
1278fd3
chore(frontend): migrate workflows to pnpm via corepack
gildesmarais Apr 3, 2026
d0d2e7b
chore(stylelint): add workspace stylelint configuration
gildesmarais Apr 3, 2026
1869988
refactor(css): align shared tokens and app styles with stylelint
gildesmarais Apr 3, 2026
c2ca87b
css: unify shared token system naming
gildesmarais Apr 3, 2026
a47b62c
css: normalize app styles to shared scale tokens
gildesmarais Apr 3, 2026
7f6b0fa
test: align storage mocks with web storage semantics
gildesmarais Apr 3, 2026
887bd23
ci: use pnpm lockfile for node setup and installs
gildesmarais Apr 3, 2026
5c305b9
Use pnpm audit in frontend CI job
gildesmarais Apr 3, 2026
c0686b1
chore: update and clean deps
gildesmarais Apr 3, 2026
3b4429c
Switch Docker frontend stage from npm to pnpm
gildesmarais Apr 3, 2026
e1456eb
Fix mobile URL field overflow in form layout
gildesmarais Apr 4, 2026
87801fd
Refine result-state hierarchy and readiness UX
gildesmarais Apr 4, 2026
cb1465e
Polish entry-panel copy and token prompt messaging
gildesmarais Apr 4, 2026
7ff7184
Keep local OpenAPI utility link on frontend origin
gildesmarais Apr 4, 2026
085f0e6
Align frontend journey with industrial minimal state frame
gildesmarais Apr 4, 2026
a6f734a
Refine readiness retry UX cadence and flatten form shell
gildesmarais Apr 4, 2026
de148cf
Fix docs auth boundaries and stabilize feed UI/test primitives
gildesmarais Apr 4, 2026
f39f1b8
git rm --cached -r skills
gildesmarais Apr 4, 2026
7f1d3f0
.
gildesmarais Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ RUN apk add --no-cache \
yaml-dev \
tzdata

RUN if ! command -v corepack >/dev/null 2>&1; then npm install -g corepack; fi \
&& corepack enable \
&& corepack prepare "pnpm@latest" --activate
Comment on lines +20 to +21
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The devcontainer forces pnpm@latest via corepack prepare, but frontend/package.json pins a specific pnpm version via packageManager. Activating latest can drift from the pinned version and cause lockfile / CI mismatches. Prefer preparing/activating the pinned version (or just corepack enable and let Corepack honor packageManager).

Suggested change
&& corepack enable \
&& corepack prepare "pnpm@latest" --activate
&& corepack enable

Copilot uses AI. Check for mistakes.

ARG USER=vscode
ARG UID=1000
ARG GID=1000
Expand Down
7 changes: 6 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
"vscode": {
"extensions": [
"rebornix.ruby",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"prettier.configPath": "./frontend/prettier.config.js",
"ruby.format": "rubocop",
"ruby.lint": { "rubocop": true },
Expand Down
50 changes: 34 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,20 @@ jobs:
with:
bundler-cache: true

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
cache: true
cache_dependency_path: frontend/pnpm-lock.yaml
package_json_file: frontend/package.json

- name: Setup Node.js for OpenAPI lint tooling
uses: actions/setup-node@v6
with:
node-version-file: ".tool-versions"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies for OpenAPI client verification
run: npm ci
run: pnpm install --frozen-lockfile
working-directory: frontend

- name: Verify generated OpenAPI spec and client are up to date
Expand All @@ -79,33 +84,41 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
cache: true
cache_dependency_path: frontend/pnpm-lock.yaml
package_json_file: frontend/package.json

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: ".tool-versions"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci
run: pnpm install --frozen-lockfile

- name: Typecheck frontend
run: npm run typecheck
run: pnpm run typecheck

- name: Lint CSS with Stylelint
run: pnpm exec stylelint "**/*.css"

- name: Check formatting
run: npm run format:check
run: pnpm run format:check

- name: Audit dependencies
run: npm audit --audit-level=moderate
run: pnpm audit --audit-level=moderate

- name: Run frontend tests
run: npm run test:ci
run: pnpm run test:ci

- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
run: pnpm exec playwright install --with-deps chromium

- name: Run frontend smoke test
run: npm run test:e2e
run: pnpm run test:e2e

docker-build-smoke-image:
needs:
Expand Down Expand Up @@ -175,19 +188,24 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
cache: true
cache_dependency_path: frontend/pnpm-lock.yaml
package_json_file: frontend/package.json

- name: Setup Node.js for Docker build
uses: actions/setup-node@v6
with:
node-version-file: ".tool-versions"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies
run: npm ci
run: pnpm install --frozen-lockfile
working-directory: frontend

- name: Build frontend static assets
run: npm run build
run: pnpm run build
working-directory: frontend

- name: Set up QEMU
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
# Ignore rack cache
/tmp/rack-cache-*


# Ignore frontend build output and tooling caches
/frontend/dist/
/frontend/node_modules/
Expand All @@ -54,3 +53,4 @@

.yardoc
frontend/.astro
.pnpm-store
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ ARG NODE_BASE_IMAGE=node:22-alpine@sha256:8094c002d08262dba12645a3b4a15cd6cd627d
FROM ${NODE_BASE_IMAGE} AS frontend-builder

WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY frontend/ ./
RUN npm run build
RUN pnpm run build

# Stage 2: Ruby Build
FROM ${RUBY_BASE_IMAGE} AS builder
Expand Down
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ gem 'parallel'
gem 'rack-cache'
gem 'rack-timeout'
gem 'roda'
gem 'ssrf_filter'
gem 'zeitwerk'

gem 'puma', require: false
Expand Down
15 changes: 6 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ GEM
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2026.0317)
minitest (6.0.2)
mime-types-data (3.2026.0331)
minitest (6.0.3)
drb (~> 2.0)
prism (~> 1.5)
net-http (0.9.1)
Expand Down Expand Up @@ -219,7 +219,7 @@ GEM
protocol-http2 (0.24.0)
protocol-hpack (~> 1.4)
protocol-http (~> 0.47)
protocol-rack (0.22.0)
protocol-rack (0.22.1)
io-stream (>= 0.10)
protocol-http (~> 0.58)
rack (>= 1.0)
Expand Down Expand Up @@ -339,7 +339,6 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
ssrf_filter (1.4.0)
stackprof (0.2.28)
stringio (3.2.0)
thor (1.5.0)
Expand Down Expand Up @@ -394,7 +393,6 @@ DEPENDENCIES
ruby-lsp
sentry-ruby
simplecov
ssrf_filter
stackprof
vcr
webmock
Expand Down Expand Up @@ -460,8 +458,8 @@ CHECKSUMS
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
metrics (0.15.0) sha256=61ded5bac95118e995b1bc9ed4a5f19bc9814928a312a85b200abbdac9039072
mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
mime-types-data (3.2026.0317) sha256=77f078a4d8631d52b842ba77099734b06eddb7ad339d792e746d2272b67e511b
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
mime-types-data (3.2026.0331) sha256=e9942b1fac72532e2b201b0c32c52e7650ef5ef8ca043a5054674597795c97a5
minitest (6.0.3) sha256=88ac8a1de36c00692420e7cb3cc11a0773bbcb126aee1c249f320160a7d11411
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
nokogiri (1.19.2-aarch64-linux-gnu) sha256=c34d5c8208025587554608e98fd88ab125b29c80f9352b821964e9a5d5cfbd19
Expand All @@ -481,7 +479,7 @@ CHECKSUMS
protocol-http (0.60.0) sha256=ca1354947676d663b6f23c49654aee464288774e7867c4a6e406fecce9691cec
protocol-http1 (0.37.0) sha256=5bdd739e28792b341134596f6f5ab21a9d4b395f67bae69e153743eb0e69d123
protocol-http2 (0.24.0) sha256=65327a019b7e36d2774e94050bf57a43bb60212775d2fcf02ae1d2ed4f01ef28
protocol-rack (0.22.0) sha256=b7c49c0b597ca2c6d20f8bcd746c4415a1b750eacfbe64f828e780c978a4293d
protocol-rack (0.22.1) sha256=1185d245927ef9849a603700d6991ca353bc89724fbf98efa4a4333ed62a9fc3
protocol-url (0.4.0) sha256=64d4c03b6b51ad815ac6fdaf77a1d91e5baf9220d26becb846c5459dacdea9e1
protocol-websocket (0.20.2) sha256=c41d93c35fba5dae85375c597f76975f3dbd75d8c5b2f21b33dab4dc22a5a511
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
Expand Down Expand Up @@ -526,7 +524,6 @@ CHECKSUMS
simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
ssrf_filter (1.4.0) sha256=64634f7955836808244d9ce65800af682803000b8eb15a5d573df25ab3c5422b
stackprof (0.2.28) sha256=4ec2ace02f386012b40ca20ef80c030ad711831f59511da12e83b34efb0f9a04
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
Expand Down
34 changes: 20 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ setup: ## Full development setup
fi
@mkdir -p tmp/rack-cache-body tmp/rack-cache-meta
@echo "Setting up frontend..."
@cd frontend && npm install
@cd frontend && CI=1 pnpm install --frozen-lockfile
@echo "Setup complete!"

dev: ## Start development server with live reload
Expand All @@ -29,26 +29,26 @@ dev-ruby: ## Start Ruby server only
@bin/dev-ruby

dev-frontend: ## Start frontend dev server only
@cd frontend && npm run dev
@cd frontend && pnpm run dev

test: ## Run all tests (Ruby + Frontend)
bundle exec rspec
@cd frontend && npm run test:ci
@cd frontend && pnpm run test:ci

test-ruby: ## Run Ruby tests only
bundle exec rspec

test-frontend: ## Run frontend tests only
@cd frontend && npm run test:ci
@cd frontend && pnpm run test:ci

test-frontend-unit: ## Run frontend unit tests only
@cd frontend && npm run test:unit
@cd frontend && pnpm run test:unit

test-frontend-contract: ## Run frontend contract tests only
@cd frontend && npm run test:contract
@cd frontend && pnpm run test:contract

test-frontend-e2e: ## Run frontend Playwright smoke tests
@cd frontend && npm run test:e2e
@cd frontend && pnpm run test:e2e

check-frontend: ## Run frontend typecheck, format, and test checks
$(MAKE) lint-js
Expand All @@ -67,11 +67,15 @@ lint-ruby: ## Run Ruby linter (RuboCop) - errors when issues found
bundle exec rake yard:verify_public_docs
@echo "Ruby linting complete!"

lint-js: ## Run JavaScript/Frontend linter (Prettier) - errors when issues found
lint-js: ## Run JavaScript/Frontend linting (TypeScript + ESLint + Stylelint + Prettier) - errors when issues found
@echo "Running TypeScript typecheck..."
@cd frontend && npm run typecheck
@cd frontend && pnpm run typecheck
@echo "Running ESLint..."
@cd frontend && pnpm run lint
@echo "Running Stylelint..."
@cd frontend && pnpm exec stylelint "../public/shared-ui.css" "**/*.css"
@echo "Running Prettier format check..."
@cd frontend && npm run format:check
@cd frontend && pnpm run format:check
@echo "JavaScript linting complete!"

lintfix: lintfix-ruby lintfix-js ## Auto-fix all linting issues (Ruby + Frontend)
Expand All @@ -83,8 +87,10 @@ lintfix-ruby: ## Auto-fix Ruby linting issues
@echo "Ruby lintfix complete!"

lintfix-js: ## Auto-fix JavaScript/Frontend linting issues
@echo "Running ESLint auto-fix..."
@cd frontend && pnpm run lint:fix
@echo "Running Prettier formatting..."
@cd frontend && npm run format
@cd frontend && pnpm run format
@echo "JavaScript lintfix complete!"

quick-check: ## Fast local checks (Ruby lint/docs + frontend format/typecheck)
Expand All @@ -110,10 +116,10 @@ openapi-verify: ## Regenerate OpenAPI and fail if public/openapi.yaml or fronten
$(MAKE) openapi-client-verify

openapi-client: ## Generate frontend OpenAPI client/types from public/openapi.yaml
@cd frontend && npm run openapi:generate
@cd frontend && pnpm run openapi:generate

openapi-client-verify: ## Generate frontend OpenAPI client and fail if generated files are stale
@cd frontend && npm run openapi:verify
@cd frontend && pnpm run openapi:verify

openapi-lint: openapi-lint-redocly openapi-lint-spectral ## Lint public/openapi.yaml with Redocly and Spectral

Expand All @@ -132,5 +138,5 @@ clean: ## Clean temporary files

frontend-setup: ## Setup frontend dependencies
@echo "Setting up frontend dependencies..."
@cd frontend && npm install
@cd frontend && CI=1 pnpm install --frozen-lockfile
@echo "Frontend setup complete!"
41 changes: 35 additions & 6 deletions app/web/feeds/responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ class << self
# @param identifier [String]
# @return [String] serialized feed body.
def call(request:, target_kind:, identifier:)
feed_request = Request.call(request:, target_kind:, identifier:)
resolved_source = SourceResolver.call(feed_request)
result = Service.call(resolved_source)
normalized_identifier = feed_request.feed_name || identifier
feed_request, resolved_source, result = resolve_request(request:, target_kind:, identifier:)
body = write_response(response: request.response, representation: feed_request.representation, result:)

emit_result(target_kind:, identifier: normalized_identifier, resolved_source:, result:)
emit_response_result(target_kind:, identifier:, feed_request:, resolved_source:, result:)
body
rescue StandardError => error
emit_failure(target_kind:, identifier:, error:)
Expand All @@ -27,6 +23,39 @@ def call(request:, target_kind:, identifier:)

private

# @param request [Rack::Request]
# @param target_kind [Symbol]
# @param identifier [String]
# @return [Array<(Html2rss::Web::Feeds::Contracts::Request, Html2rss::Web::Feeds::Contracts::ResolvedSource, Html2rss::Web::Feeds::Contracts::RenderResult)>]
def resolve_request(request:, target_kind:, identifier:)
feed_request = Request.call(request:, target_kind:, identifier:)
resolved_source = SourceResolver.call(feed_request)
result = Service.call(resolved_source)
[feed_request, resolved_source, result]
end

# @param feed_request [Html2rss::Web::Feeds::Contracts::Request]
# @param identifier [String]
# @return [String]
def normalized_identifier(feed_request, identifier)
feed_request.feed_name || identifier
end

# @param target_kind [Symbol]
# @param identifier [String]
# @param feed_request [Html2rss::Web::Feeds::Contracts::Request]
# @param resolved_source [Html2rss::Web::Feeds::Contracts::ResolvedSource]
# @param result [Html2rss::Web::Feeds::Contracts::RenderResult]
# @return [void]
def emit_response_result(target_kind:, identifier:, feed_request:, resolved_source:, result:)
emit_result(
target_kind:,
identifier: normalized_identifier(feed_request, identifier),
resolved_source:,
result:
)
end

# @param response [Rack::Response]
# @param representation [Symbol]
# @param result [Html2rss::Web::Feeds::Contracts::RenderResult]
Expand Down
4 changes: 2 additions & 2 deletions bin/dev
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ cleanup() {

# Kill frontend dev server and its children
if [ ! -z "$FRONTEND_PID" ]; then
# Kill the npm process and its children
# Kill the frontend package-manager process and its children
pkill -P $FRONTEND_PID 2>/dev/null || true
kill $FRONTEND_PID 2>/dev/null || true
wait $FRONTEND_PID 2>/dev/null || true
Expand Down Expand Up @@ -79,7 +79,7 @@ fi

# Start frontend dev server
cd frontend
npm run dev &
pnpm run dev &
FRONTEND_PID=$!

# Verify frontend server started
Expand Down
Loading