Skip to content

feature: migrate Angular 9 HN PWA to React 18 + Vite + TypeScript#324

Open
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1781118697-react-migration
Open

feature: migrate Angular 9 HN PWA to React 18 + Vite + TypeScript#324
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1781118697-react-migration

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 10, 2026

Copy link
Copy Markdown

Summary

Full rewrite of the Angular 9 Hacker News PWA to React 18 + TypeScript + Vite, replacing the Angular CLI/Webpack toolchain, RxJS-based services, and Angular Router with modern React equivalents. Behavior, routing, theming, and PWA support are preserved 1:1.

Key mappings:

Angular React
HackerNewsAPIService (RxJS Observable + unfetch) src/services/hackernews-api.ts — plain async/await + native fetch
SettingsService (RxJS BehaviorSubject) src/contexts/SettingsContext.tsx — Context + useSettings() hook
comment.pipe.ts src/utils/formatComment.ts
Angular Router (app.routes.ts) react-router-dom v6 <Routes> in src/App.tsx
class models interface/type in src/models/
Component .ts + .html + .scss Function components + SCSS in src/components & src/pages
Angular CLI / Webpack Vite + @vitejs/plugin-react
Angular service worker / manifest.json vite-plugin-pwa

Notable details:

  • Theme engine preserved: the default / night / amoledblack class is applied to the root <div> from settings, so the existing theme SCSS mixins continue to work unchanged. prefers-color-scheme: dark is subscribed via useEffect + matchMedia.
  • Poll aggregation from fetchItemContent is preserved — for type === 'poll' items it fetches each option and sums poll_votes_count via Promise.all.
  • Recursive comments: Comment.tsx renders comment.comments recursively with a local collapse useState; HTML content rendered via dangerouslySetInnerHTML (matching Angular's [innerHTML]).
  • Lazy routes: /item/:id and /user/:id are React.lazy + <Suspense fallback={<Loader />}>.
  • GA pageviews: a usePageViews hook calls ga('send', 'pageview') on useLocation() changes.

Removed Angular-specific files: angular.json, karma.conf.js, tslint.json, e2e/, src/polyfills.ts, src/test.ts, src/main.ts, and the src/app/ tree.

Verification

  • npm run lint, npm run typecheck, npm run build all pass.
  • Manually verified in-browser: feed pages render, item details with nested collapsible comments, settings modal (theme/font/spacing/new-tab), and theme switching (default → night).

Link to Devin session: https://app.devin.ai/sessions/7e2b9c9ef4ff4c8eb5587812e54efa52
Requested by: @lburgers


Devin Review

Status Commit
🟢 Reviewed b97dcbc
Open in Devin Review (Staging)

Co-Authored-By: Lukas Burger <lukaskburger@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review (Staging)
Debug

Playground

Comment thread src/services/hackernews-api.ts
Comment on lines +53 to +60
useEffect(() => {
const media = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (event: MediaQueryListEvent) => {
setTheme(event.matches ? 'night' : 'default');
};
media.addEventListener('change', handleChange);
return () => media.removeEventListener('change', handleChange);
}, [setTheme]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 System color scheme listener overrides user's explicitly saved theme

The useEffect at src/contexts/SettingsContext.tsx:53-60 subscribes to system color scheme changes and calls setTheme(), which writes to localStorage. If a user explicitly selected the 'amoledblack' theme and later the OS switches between light/dark mode, the listener will overwrite their saved preference with either 'night' or 'default'. This was the same behavior in the old Angular SettingsService (src/app/shared/services/settings.service.ts:28-35), so it's a pre-existing design issue carried forward. A fix would be to only react to system changes when no explicit user preference is saved.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Intentionally preserving this behavior for now. As you noted, it mirrors the original Angular SettingsService (src/app/shared/services/settings.service.ts:28-35), and the migration spec explicitly asks to replicate that logic 1:1, so I kept it identical to avoid behavioral drift in this PR. The "only react to system changes when no explicit preference is saved" improvement is a reasonable follow-up, but it's a behavior change beyond the scope of this migration — happy to do it in a separate PR if desired.

devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: Lukas Burger <lukaskburger@gmail.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

import type { User } from '../models/user';
import type { PollResult } from '../models/poll-result';

const baseUrl = 'https://node-hnapi.herokuapp.com';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🚩 API base URL points to Heroku free-tier which was sunset in 2022

The baseUrl at src/services/hackernews-api.ts:5 is https://node-hnapi.herokuapp.com. Heroku removed its free tier in November 2022, so this endpoint may be unavailable. This is a pre-existing issue (the old Angular service used the same URL), not introduced by this PR, but the migration would have been a good opportunity to switch to a maintained API (e.g., the official HN Firebase API at https://hacker-news.firebaseio.com).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Keeping this as-is. The base URL is specified verbatim in the migration spec, and it's still live — I verified end-to-end during testing that feeds, item details, comments, and user pages all load real data from https://node-hnapi.herokuapp.com. Switching to the official HN Firebase API would change the data-fetching shape (it returns item IDs requiring per-item fan-out rather than ready-made feed/comment trees), which is out of scope for a 1:1 migration. Good candidate for a follow-up if the endpoint is ever retired.

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 0 new potential issues.

Open in Devin Review (Staging)
Debug

Playground

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant