Skip to content

Reference Implementation: Rails Frontend (Legacy UI)

Overview

The Nextpoint frontend is a server-rendered Rails application with selective React components for interactive features. It runs a dual JavaScript pipeline: Sprockets (legacy jQuery/AG Grid) alongside Webpacker (React 18). The app is ~97% server-rendered ERB with React mounted into specific containers.

Architecture

rails/
├── app/javascript/                      # Webpacker (React 18)
│   ├── packs/
│   │   ├── application.js               # Main entry point
│   │   └── components.js                # React bridge via react_ujs + createRoot
│   ├── components/                      # ~105 JSX files (atomic design)
│   │   ├── atoms/                       # 21 — Button, Badge, Checkbox, Spinner, Tooltip, etc.
│   │   ├── molecules/                   # 17 — BatesStampModal, CustomAnnotationSidebar, charts
│   │   ├── organisms/                   # 25 — Batch details (12), doc viewer toolbars (7), imports (5)
│   │   ├── containers/                  # 10 — SearchTermsGrid, report modals, bulk actions
│   │   ├── pages/                       # 5 — BatchDetailsPage, ImportsPage, SplitBatchDetailsPage
│   │   ├── templates/                   # 4 — CardContainer, EditorModal, ModalContainer, PageContainer
│   │   ├── utils/                       # 10 — Annotation helpers, PDF utilities, stamp utilities
│   │   ├── api/                         # 7 — Fetch wrappers for viewer, stamps, imports
│   │   ├── DocumentViewPdf.jsx          # Read-only PDF viewer (Legacy cases)
│   │   ├── NgeDocumentViewPdf.jsx       # Full NGE PDF editor (~52KB, Nutrient/PSPDFKit)
│   │   ├── DocumentToolbar.jsx          # Document toolbar (~74KB, largest component)
│   │   ├── AnalyticsTab.jsx             # Analytics dashboard
│   │   └── ExhibitStampEditor.jsx       # Stamp editing
│   └── channels/                        # Action Cable (WebSocket)
├── app/assets/
│   ├── javascripts/                     # Sprockets (legacy, 156 .js files)
│   │   ├── documents/                   # Document grid, coding panel, viewer bridge
│   │   │   ├── documents_grid.js        # Main document grid (AG Grid)
│   │   │   ├── document_view_pdf.js     # Legacy viewer bridge → React
│   │   │   └── coding_panel.js          # Coding/review panel
│   │   ├── chatbot.js                   # AI chatbot
│   │   ├── ai_assistant.js              # AI assistant
│   │   ├── chronology/                  # Chronology/timeline
│   │   ├── image_markups/               # Markup tools
│   │   ├── case_folder_uploader.js      # S3 upload
│   │   └── vendor/                      # jQuery 2.2.2, AG Grid Enterprise, Handlebars, etc.
│   └── stylesheets/                     # 85 plain CSS files (no Sass/SCSS)
│       ├── components/                  # Atomic: atoms/, molecules/, organisms/, pages/
│       ├── application.css              # Sprockets manifest
│       └── vendor/                      # jQuery UI, IcoMoon, Select2, AG Grid themes
├── app/views/                           # 914 ERB templates across 101 directories
└── package.json                         # React 18, AG Grid 30, Webpacker 5.4.4, Webpack 4

Dual JavaScript Pipeline

Sprockets (Legacy — 156 files)

The original JS pipeline, still serving the majority of the application:

Framework Version Usage
jQuery 2.2.2 DOM manipulation, AJAX, event handling
jQuery UI Dialogs, datepickers, drag/drop, sortable
AG Grid Enterprise vendored Document grid (main review interface)
Handlebars 4.7.7 Client-side templating for grid cells
Moment.js Date formatting
Select2 Enhanced dropdowns
Flatpickr Date pickers
DataTables 1.11.3 Tabular data (non-grid views)
SpreadJS Spreadsheet viewer
PapaParse CSV parsing
jsPDF + pdfmake Client-side PDF generation
html2canvas Screenshot capture
DOMPurify XSS sanitization

Webpacker (React 18 — ~105 JSX files)

Modern React components mounted into ERB via react_component helper:

Package Version Usage
React / React DOM ^18.3.1 UI framework
react_ujs ^2.2.1 Rails-React bridge (auto-mount)
react-router-dom ^6.26.2 Client routing (batch details only)
AG Grid React ^30.2.1 React grid wrapper
Chart.js + react-chartjs-2 ^4.4.6 Analytics charts
axios ^1.7.9 HTTP client
react-select ^5.10.2 Enhanced dropdowns
react-hot-toast ^2.5.2 Toast notifications

React-ERB Integration Pattern

ERB Template                          React Component
─────────────                         ───────────────
<%= react_component(                  class MyComponent extends React.Component {
  "MyComponent",                        render() { return <div>...</div> }
  { prop1: @data }                    }
) %>
    │                                      ▲
    ▼                                      │
<div data-react-class="MyComponent"   react_ujs scans DOM on page load,
     data-react-props='{"prop1":...}'  finds data-react-class attrs,
/>                                     mounts with createRoot (React 18)

Key patterns: - ERB → React: react_component("Name", {props}) in ERB templates - Global bridges: window.NP (legacy config/data) and window.nextpoint (React viewer APIs) - Legacy → React reload: window.nextpoint.reloadReactViewer() re-mounts after jQuery DOM changes - Server data bootstrap: Rails controllers pass data as React props OR as window.NP.bootstrapped_* globals - No SPA: React Router only in batch details (BatchDetailsRouter.jsx). All other navigation is full-page ERB.

React Component Categories

Document Viewer (NGE vs Legacy — the biggest divergence)

Component Size Purpose
NgeDocumentViewPdf.jsx ~52KB Full NGE editor: Nutrient/PSPDFKit, JWT auth, annotations, stamps
DocumentViewPdf.jsx Smaller Read-only Legacy viewer: client-side PDF load, search, toolbar
DocumentToolbar.jsx ~74KB Toolbar: page ops, markups, export, processing_in_nge locking

Nutrient/PSPDFKit integration: - Loaded from CDN (useCDN: true), licensed via NP.nutrient_license_key - NGE mode: server-backed via JWT (nutrient_jwt_controller.rb), AutoSaveMode.IMMEDIATE - Legacy mode: client-side read-only PDF loading - Legacy bridge: document_view_pdf.js (Sprockets) calls window.nextpoint.DocumentViewPdf.init()

Batch/Import (React pages)

Component Purpose
BatchDetailsPage.jsx Batch detail view with router
ImportsPage.jsx Import configuration
SplitBatchDetailsPage.jsx Split batch view
12 organism components Batch tabs, events, settings, progress

Search/Analytics

Component Purpose
AnalyticsTab.jsx Analytics dashboard with Chart.js
SearchTermsGrid.jsx Search terms management (AG Grid)

Coding/Stamps

Component Purpose
ExhibitStampEditor.jsx Bates/confidentiality stamp editing
BatesStampModal.jsx Stamp configuration modal
CustomAnnotationSidebar.jsx Annotation sidebar

CSS Architecture

  • 85 plain CSS files — no Sass, no SCSS, no CSS-in-JS, no Tailwind
  • Sprockets manifest (application.css) pulls in all files
  • Component CSS follows atomic design: components/atoms/, molecules/, organisms/, pages/
  • AG Grid themes: ag-theme-alpine + ag-grid-balham
  • NGE vs Legacy body class: <body class="nge"> or <body class="legacy"> enables global CSS targeting

NGE vs Legacy UI Summary

Aspect NGE Legacy
Document viewer NgeDocumentViewPdf.jsx (Nutrient, JWT, annotations) DocumentViewPdf.jsx (read-only PDF)
Page thumbnails Hidden (Nutrient renders pages) S3 page images shown
Toolbar DocumentToolbar.jsx with processing_in_nge locking Standard toolbar
Body class nge legacy
Import flow React ImportsPage + different format options ERB-based multi-step
Batch details React BatchDetailsPage with Athena event tracking ERB with Legacy event display
Wire/Exchange Hidden jQuery-based wire workflow

Key Architectural Observations

  1. ~97% server-rendered — React is an overlay, not a replacement. Most pages are ERB.
  2. No TypeScript — all React is plain JSX with prop-types
  3. No test framework for frontend — no Jest, no React Testing Library in package.json
  4. Two grid systems — AG Grid Enterprise (vendored in Sprockets) AND AG Grid React (via npm)
  5. Largest components are NGE-related — DocumentToolbar.jsx (74KB) and NgeDocumentViewPdf.jsx (52KB)
  6. jQuery and React coexist — jQuery handles the document grid, coding panel, chronology, markups; React handles batch details, document viewer, analytics
  7. Global state via window — no Redux, no Context API for cross-component state. window.NP is the global data store.

Modernization Plan

See ADR-004: Incremental Frontend Modernization for the full decision record.

Strategy: Grow React From the Inside Out — No Big Rewrite

The frontend modernization follows an incremental approach with zero dedicated rewrite sprints. The key insight: 914 ERB templates are fine. Server-rendered HTML works for ~90% of Nextpoint's pages. React should only exist where rich client interactivity is needed.

Phase 0: Foundation (1-2 sprints, one engineer)

Replace the build toolchain without changing application code:

1. Replace Webpacker 5 → jsbundling-rails (esbuild)
   - Webpacker 5 / Webpack 4 are EOL (no security patches)
   - esbuild builds in <1 second vs Webpack's 30+ seconds
   - Rails 7 officially recommends jsbundling-rails
   - Build-tool swap only — zero React component code changes

2. Add TypeScript (tsconfig.json with allowJs: true)
   - Zero files need to change on day 1
   - New files → .tsx, existing files stay .jsx
   - Rename .jsx → .tsx when touching a file for other reasons

Phase 1: Safety Net (ongoing, no extra sprints)

Boy Scout Rule — leave code better than you found it:

When fixing a bug in a React component   → add a test for that bug
When adding a feature to a React component → add TypeScript to that file
When touching CSS                         → no changes needed (plain CSS works)

No dedicated "add tests" or "add TypeScript" sprints.

Phase 2: New Features (ongoing, natural)

New interactive feature  → React component (mount in ERB via react_component)
New settings/admin page  → ERB (server-rendered is fine)
New data visualization   → React + Chart.js

Phase 3: Convert on Contact (opportunistic, no deadline)

When a jQuery page needs significant rework, convert to React. Priority order:

Priority Page Reason
1 Coding panel Heavy state, frequent feature requests, most dev friction
2 Import wizard Already partially React, natural to complete
3 Export configuration Forms + validation, benefits from React state
4 Document grid LAST — huge, working, AG Grid handles it well

What NOT to Do

Avoid Reason
Rewrite all jQuery to React Massive effort, zero user value, high regression risk
Add Redux/Zustand React Context suffices for component tree size
Adopt Next.js/Remix/SPA Would require API layer that doesn't exist
Component library upfront Build as you go, extract at ≥3 uses
Set deadline for modernization Permanent practice, not a project
Rewrite ERB to React Server-rendered HTML is correct for 90% of pages

When to Use What

ERB:    Static content, simple forms, read-only pages, admin panels
React:  Rich interactivity, drag/drop, real-time updates, complex state, data viz
jQuery: It works and nobody is actively changing it (don't fix what isn't broken)

Estimated Cost

Phase 0:  1-2 sprints (one engineer) — build tool swap + tsconfig
Phase 1:  0 extra time — part of normal bug fixes
Phase 2:  0 extra time — part of normal feature work
Phase 3:  Variable — only when touching those pages anyway

Key File Locations

File Purpose
app/javascript/packs/application.js Webpacker entry point
app/javascript/packs/components.js React bridge with createRoot
app/javascript/components/NgeDocumentViewPdf.jsx NGE PDF editor (Nutrient)
app/javascript/components/DocumentViewPdf.jsx Legacy PDF viewer
app/javascript/components/DocumentToolbar.jsx Main toolbar (74KB)
app/assets/javascripts/documents/documents_grid.js AG Grid document grid
app/assets/javascripts/documents/coding_panel.js Review coding panel
app/assets/javascripts/documents/document_view_pdf.js Legacy → React viewer bridge
app/views/layouts/application.html.erb Main layout (body class nge/legacy)
config/webpacker.yml Webpacker config
package.json JS dependencies
Ask the Architecture ×

Ask questions about Nextpoint architecture, patterns, rules, or any module. Powered by Claude Opus 4.6.