Modern reimplementation using React + Vite + TypeScript, MUI + Tailwind, React Query, Vitest, Playwright, MSW.
- Dark / Light mode with system preference detection & persistence (
localStorage: hn-color-mode). - Layout density toggle (Comfort / Compact) with persistence (
localStorage: hn-layout-mode) and dynamic spacing. - Responsive two-column layout at medium breakpoints with graceful single column fallback.
- Relative time utility (
timeAgo) for story timestamps. - Prefetching of next page story batch via React Query to reduce perceived latency when paginating.
- Error Boundary wrapping the stories list provides an isolated crash surface with retry.
- Shimmer skeleton placeholders (wave animation) extracted into a reusable
StorySkeletoncomponent. - Accessibility: in-app skip link to main content, labeled list landmarks, focus-visible outlines, preserved accessible names on toggles.
- Test Strategy: Collocated unit tests for hooks/components, a11y tests, Playwright E2E for dark mode persistence.
npm install
npm run dev
Visit http://localhost:5173
- dev: Vite dev server
- build: Type check then build
- preview: Preview production build
- test: Run unit/integration tests (Vitest)
- test:ui: Interactive Vitest UI
- test:e2e: Playwright end-to-end tests
- lint / format: Code quality
See implementation-plan.md for full phased plan.
Core libraries:
- React 18 + Vite (fast dev, ESM build)
- MUI + Tailwind (component library plus utility classes; dark mode via root
class+ MUI theme) - React Query (data fetching, caching, prefetching)
- MSW (mock service worker—handlers scaffolded; can extend for offline/dev stability)
- Vitest + Testing Library (unit / integration), Playwright (E2E)
Color mode and layout density each have dedicated context providers (ColorModeProvider, LayoutModeProvider). Both persist the current selection to localStorage and rehydrate on load. Color mode also listens to system preference changes when no explicit user choice exists.
// Toggle dark mode
const { mode, toggle } = useColorMode();
// Toggle layout density
const { layout, toggleLayout } = useLayoutMode();Tailwind's dark: utilities are enabled by syncing a dark class on <html>. Density is exposed as data-density on <body> and per story item for CSS or test hooks.
An ErrorBoundary (class component) wraps StoriesList to isolate failures in story fetching/rendering. On error it renders an alert with a Retry button that resets internal error state.
Initial and incremental loads show a set of StorySkeleton components using MUI Skeleton with animation="wave". Density setting adjusts their spacing and widths for visual alignment with loaded content.
- Skip link (first focusable element visually appears on focus) jumps to
#main-contentand programmatically focuses the main region. - Landmark labeling: list gets an
aria-labelderived from feed (e.g.,top stories list). - Focus outlines enhanced via CSS for keyboard visibility.
- All interactive toggle buttons have explicit
aria-labels.
- Unit / Integration: Vitest + Testing Library (colocated next to source).
- E2E: Playwright specs in
tests/e2e(e.g., dark mode persistence across reloads). - a11y basics: Tests assert skip link presence and accessible names on critical controls.
Run all tests:
npm test
E2E only:
npm run test:e2e