Skip to content

feature: Phase 1 React+TS migration — scaffold, models, and API layer#431

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

feature: Phase 1 React+TS migration — scaffold, models, and API layer#431
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1783408871-react-migration-phase1

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Phase 1 of migrating the Hacker News PWA from Angular 9 to React + TypeScript. Scope is data layer only — no UI conversion. All new code lives in a new react-app/ subdirectory; the original Angular app is untouched for reference.

Scaffold — Vite + React 18 + TypeScript in react-app/, SCSS support via sass (used in src/App.scss), and react-router-dom added now for Phase 2. Scripts: dev, build (tsc -b && vite build), preview. No Angular deps.

Models (react-app/src/models/) — ported story, user, comment, poll-result, settings, feed-type.type from src/app/shared/models/. The field-only Angular classes (Story, User, Comment, PollResult) are converted to plain interfaces so they compile cleanly under strict tsconfig (strictPropertyInitialization); Settings and FeedType were already framework-agnostic. Field names/types unchanged (including the original crated_time typo in User).

API layer (react-app/src/api/hackernews.ts) — replaces HackerNewsAPIService, dropping RxJS/unfetch for native fetch async functions. Base URL https://node-hnapi.herokuapp.com.

fetchFeed(feedType, page): Promise<Story[]>       // GET /{feedType}?page={page}
fetchItemContent(id): Promise<Story>              // GET /item/{id} + poll expansion
fetchPollContent(id): Promise<PollResult>         // GET /item/{id}
fetchUser(id): Promise<User>                       // GET /user/{id}

Poll-expansion logic from the original service (lines 24-37) is preserved in fetchItemContent:

if (story.type === 'poll') {
  const n = story.poll.length;
  story.poll_votes_count = 0;
  for (let i = 1; i <= n; i++) {
    const pr = await fetchPollContent(story.id + i);
    story.poll[i - 1] = pr;
    story.poll_votes_count += pr.points;
  }
}

(RxJS subscribe side-effects replaced with sequential await; a getJson helper throws on non-2xx.)

Verification

  • npm run build succeeds (tsc -b type-checks the API against the ported models, then vite build).
  • Smoke test via tsx: fetchFeed('news', 1) returns 30 stories and fetchItemContent(id) returns a populated item.
  • Note: the public node-hnapi.herokuapp.com/user/:id endpoint currently returns 404 upstream (Cannot GET /user/pg) — /news and /item work. fetchUser mirrors the original service's URL exactly; the 404 is an upstream API limitation, not a code issue.

Out of scope (later phases)

Routing, theming, feeds, item details, user profiles, PWA — no UI components converted.

Note on target repo

The task referenced ankehao-demo/angular2-hn, but the git integration returns 403 on that repo, so this PR targets the accessible COG-GTM/angular2-hn fork per the repo's forked-PR workflow.

Link to Devin session: https://app.devin.ai/sessions/1ba0e16747064c199c190e75d0dfb895
Requested by: @matthewguerra-cog


Devin Review

Status Commit
⚪ Not started

Run Devin Review

Open in Devin Review (Staging)

Co-Authored-By: Matthew Guerra <matthew.guerra@cognition.ai>
@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

@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 4 potential issues.

Open in Devin Review

Comment thread react-app/src/models/story.ts Outdated
@@ -0,0 +1,8 @@
export interface User {
id: string;
crated_time: number;

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.

🔍 Typo 'crated_time' copied from original Angular code

The User interface has a field named crated_time (react-app/src/models/user.ts:3), which is almost certainly meant to be created_time. This typo exists in the original Angular model at src/app/shared/models/user.ts:3 and was faithfully copied. Since this is a fresh port into new code, this is a good opportunity to fix the field name — assuming the upstream API doesn't actually use the misspelled key. If the API does return crated_time, renaming would break the mapping.

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 crated_time as-is for now. The upstream /user/:id endpoint currently 404s (Cannot GET /user/pg), so I can't verify whether the API returns the misspelled key or not — and as you note, renaming would break the mapping if it does. Since this is a plain structural interface (no runtime field mapping), preserving the original field name is the safe choice for this faithful Phase 1 port. Worth revisiting in the Phase 4 user-profile work once a working user endpoint is confirmed.

import { User } from '../models/user';
import { 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 discontinued

The API base URL https://node-hnapi.herokuapp.com (react-app/src/api/hackernews.ts:5) relies on Heroku's free dynos, which were discontinued in November 2022. This URL is likely no longer functional. The same URL exists in the original Angular service (src/app/shared/services/hackernews-api.service.ts:16). For the React port to actually work, this should be updated to a working HN API endpoint (e.g., the official Firebase HN API at https://hacker-news.firebaseio.com/v0/ or a self-hosted alternative like https://api.hnpwa.com/v0/).

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.

This URL is actually still functional — I smoke-tested it this session: GET /news?page=1 and GET /item/{id} both return 200 with real data (e.g. 30 stories, populated item + comments). Only the /user/:id endpoint 404s. Also, the base URL https://node-hnapi.herokuapp.com was explicitly specified as a Phase 1 requirement, so I'm keeping it. Migrating to a different HN API (Firebase / hnpwa) would be a separate, deliberate change — flagging for the user to decide in a later phase.

Comment on lines +25 to +29
for (let i = 1; i <= numberOfPollOptions; i++) {
const pollResults = await fetchPollContent(story.id + i);
story.poll[i - 1] = pollResults;
story.poll_votes_count += pollResults.points;
}

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.

📝 Info: Poll option fetches are sequential; could be parallelized

In fetchItemContent (react-app/src/api/hackernews.ts:25-29), each poll option is fetched sequentially with await inside a for loop. This is actually an improvement over the original Angular code which fired off subscriptions inside a map operator (fire-and-forget, meaning the story was returned before poll data arrived). However, the sequential approach means N poll options take N round trips. These fetches are independent and could be parallelized with Promise.all for better performance:

const results = await Promise.all(
  story.poll.map((_, i) => fetchPollContent(story.id + i + 1))
);
results.forEach((r, i) => { story.poll[i] = r; story.poll_votes_count += r.points; });
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.

Agreed the sequential fetches could be parallelized with Promise.all. For Phase 1 the goal was a faithful port of the original poll-expansion logic (the task explicitly asked to replicate it), so I kept it sequential and readable. The Promise.all optimization is a good follow-up for a later phase — noting it here.

Co-Authored-By: Matthew Guerra <matthew.guerra@cognition.ai>

@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.

Open in Devin Review

points: number;
user: string;
time: number;
time_ago: string;

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.

📝 Info: Story model's time_ago type corrected from number to string during port

The original Angular Story model (src/app/shared/models/story.ts:11) declares time_ago: number, but the HN API returns a human-readable string like "3 hours ago" and it's rendered directly in templates (src/app/feeds/item/item.component.html:19). The React port correctly changes this to time_ago: string, which is a deliberate fix of a pre-existing type error in the original codebase.

Open in Devin Review

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

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.

0 participants