Skip to content

feature: port feeds (FeedList, FeedItem) to React (Workstream 2) - #918

Merged
devin-ai-integration[bot] merged 2 commits into
devin/1792603540-react-foundationfrom
devin/1792603717-react-feeds
Oct 21, 2026
Merged

devin-ai-integration[bot] merged 2 commits into
devin/1792603540-react-foundationfrom
devin/1792603717-react-feeds

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Summary

Ports Angular feeds/feed and feeds/item to react-app/src/features/feeds/ (Workstream 2 of the React migration; base is the foundation branch from #917). Only files under features/feeds/** change.

FeedList (/news|newest|show|ask|jobs/:page)

  • useScrollToTop(${feedType}/${page}) first, then history.scrollRestoration = 'manual' while mounted (previous value restored on unmount), so More/Back from the bottom doesn't leave a blank viewport.
  • useFeed(feedType, page): <Loader /> while pending; <ErrorMessage message={Could not load ${feedType} stories.} /> on error. Invalid pages (abc, 0) are errors and make no request.
  • Same as Angular: <ol start={(page-1)*30+1}> and .list-margin on every feed except jobs. Jobs also gets the .job-header Triplebyte paragraph. "‹ Prev" shows when listStart !== 1; "More ›" only when the page has exactly 30 items.

FeedItem

  • externalHref(url): external <a> only for a parseable http(s) URL that passes isSafeHref. Anything else (item?id=N Ask/Show posts, javascript:, malformed) becomes <Link to="/item/:id">, like Angular's hasUrl fallback. openLinkInNewTab adds target="_blank" rel="noopener noreferrer".
  • .subtext-palm (mobile) and .subtext-laptop variants, job rows (time only), commentLabel for the comment links.
  • Settings: titleFontSizepx on the two title links only; listSpacingpx as the row's margin-bottom.
  • Whitespace copies what Angular keeps after it collapses whitespace: Title (domain) with the space inside the title anchor, 3 hours ago • N comments, 120 points by pg + .item-details padding + 3 hours ago | N comments, and ‹ Prev + 20px + More ›.

SCSS: FeedList.scss and FeedItem.scss port the Angular files rule for rule, scoped with :where(.main-content.feed) and :where(.item-block) so the theme rules still win. Angular's feed a { font-weight: bold } only ever reached the feed's own links, so it is scoped to .job-header a, .nav a and doesn't bold the story rows.

Tests (replace the placeholders): loader (deferred), exact error text for all 5 feeds, invalid page, non-list body, numbering across pages, Prev/More rules, jobs header, More click and keyboard pagination, scroll-to-top, scrollRestoration, URL classification, new-tab attrs, settings effects, job rows, whitespace text, the commentLabel cases.

Shared changes needed

None.

Devin-Org: engineering

Link to Devin session: https://devin-gtm.devinenterprise.com/sessions/cd07a47aec7c48daa5e331248f55d0bd
Open in Devin Desktop: https://devin-gtm.devinenterprise.com/desktop/session/cd07a47aec7c48daa5e331248f55d0bd?variant=devin
Requested by: @ishita-cog


Note

Devin errored when opening this Pull Request as ishita-cog.
As a fallback, Devin opened this PR as itself.


Devin Review

Status Commit
⚪ Not started —

Run Devin Review

Devin Review (Staging)


Devin Review

…d tests

Co-Authored-By: Ishita Gupta <ishita.gupta@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

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

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

…ormalization)

Co-Authored-By: Ishita Gupta <ishita.gupta@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Author

Side-by-side parity test: Angular (:4300, origin/master) vs React (:4200), live HN data

Compared all 5 feeds (news, newest, show, ask, jobs) at 1280px and 375px in default, night and amoledblack, looking at the feed content only (the header/footer are still placeholders from the foundation branch). In all 30 combinations both apps show the same live titles in the same order, with matching numbering, typography, borders, padding, row width/height and responsive metadata. The largest text-gap difference is 0.0156px (title→domain is 4.44px in both, Prev→More is 20px in both). The mobile list margin is 55px on feeds and 0px on jobs in both. Visited title colours match.

  • Clicking More goes to /news/2, numbering starts at 31, Prev appears and the page lands at scrollY=0. Browser Back from the bottom of page 2 returns to the top of page 1 with stories visible, not a blank screen (history.scrollRestoration="manual").
  • /jobs/2 has 1 row, numbered 31, with Prev and no More.
  • Ask item?id=N titles go to /item/N. External titles go to the original URL and open in a new tab with rel="noopener noreferrer".
  • Settings: titleFontSize=24 changes feed titles only (domain stays 15px, metadata 12px). listSpacing=20 gives a 20px margin-bottom per row.
  • Keyboard: Tab goes title → user → comments → … → Prev → More, and Enter follows each link.
  • Errors: React /news/abc shows "Could not load news stories." without making a request. When the feed API is blocked, both apps show their error state.

Desktop, default theme (Angular left, React right)
news 1280 default

Mobile, night theme (Angular left, React right)
news 375 night

Jobs: 1280 amoledblack, 375 default
jobs 1280 amoledblack
jobs 375 default

Last page and settings
terminal page
settings

Recording: pagination, Back from the bottom, keyboard
recording

Fixed after testing: React was normalizing external hrefs (https://x.com → https://x.com/). It now uses item.url verbatim, as Angular does (e49378c).

Parity notes, not React bugs: Angular's /news/abc requests page=NaN and still renders stories, while React shows the error page as the migration contract requires. Angular uses rel="noopener" and React adds noreferrer as required. Live /show/1 had no item?id= post at test time, so internal links were clicked through on Ask (unit tests cover Show).

Written by Devin

@devin-ai-integration
devin-ai-integration Bot merged commit 2cb63ae into devin/1792603540-react-foundation Oct 21, 2026
3 checks passed

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

Devin Review

if (typeof url !== 'string' || url.indexOf('http') !== 0 || !isSafeHref(url)) return null;
try {
const parsed = new URL(url);
return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? url : null;

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.

🟡 Scheme-only story links open app routes

On an HTTPS deployment, externalHref('https:example.com') returns a link the browser resolves inside the app. The base URL makes the browser resolve this scheme-only href differently from the base-free validation, so the story never opens.

Learn more

A story URL with a scheme but no // has different meanings depending on whether the URL parser receives a base. Here new URL(url) accepts https:example.com as https://example.com/, but an anchor on an HTTPS page resolves https:example.com relative to the app's base URL. FeedItem uses the returned string directly as the anchor href. The same mismatch affects http:example.com when the app runs over HTTP.

Example: On https://app.example/news/1, a story with url: 'https:example.com' passes validation as https://example.com/. Clicking its title navigates to https://app.example/example.com instead of the story.

Recommended fix: Return parsed.href for scheme-only or otherwise base-dependent HTTP(S) inputs; preserve the original string only when it resolves to the validated destination from the document base. Add a test covering a scheme-only URL and its rendered anchor destination.

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.

Confirmed. My post-test switch to the raw url (e49378c) let https:example.com through: new URL accepts it, but as an href the browser resolves it relative to the page. Since this PR is merged, the fix is in a follow-up PR on a new branch: externalHref now requires ^https?://, so these fall back to /item/:id. Tests added.

Written by Devin

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