Skip to content

Migrate content viewer to React - #200

Open
dominickendrick wants to merge 7 commits into
mainfrom
dk-wsy-migrate-content
Open

dominickendrick wants to merge 7 commits into
mainfrom
dk-wsy-migrate-content

Conversation

@dominickendrick

@dominickendrick dominickendrick commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Note

This PR contains code generated by AI using Claude Opus 4.8. This was all reviewed by developers

What does this change?

This is the next step of the AngularJS → React migration described in rfc/react-frontend-migration.md. It migrates the snapshot content viewer (the article furniture, the sliding HTML/JSON body and the Restore / Copy / Export / toggle actions bar) to React, following the incremental bridge pattern established for the search form and sidebar. The rest of the AngularJS app keeps running unchanged.

The still-Angular SnapshotListCtrl remains authoritative for the active selection; the new React viewer loads the initial snapshot itself and then follows the existing snapshot-list:* mediator events, so keyboard navigation and the HTML/JSON toggle stay in sync across the Angular ↔ React boundary.

Migrated components

  • ContentViewer — replaces the snapshot-content block of restore-list.html and SnapshotContentCtrl, composed of:
    • ContentActions — the Restore / Copy JSON / Export (Git & Zip) / Show JSON-Text toggle bar, using Stand Button/LinkButton.
    • ContentFurniture — the headline / standfirst / trailText summary.
    • ContentPanels — the 200%-wide sliding track holding the HTML and JSON columns.
    • article/* (ArticleBody, ArticleElementView, ListElementView, TimelineElementView, RawHtml) — renders the parsed article body.
  • Adds a useSnapshotContent hook that ports SnapshotContentCtrl's behaviour: initial (index 0) load, following snapshot-list:load-content, HTML/JSON toggle round-tripped through the mediator, the fade-in on content change, restore-permission check, and copy-to-clipboard.
  • Adds a typed snapshotContent model (parseSnapshotContent) plus unit tests, replacing SnapshotModel.js.
  • Adds a fetchSnapshot API layer, replacing the legacy SnapshotService / SnapshotModels.

Clipboard

  • Copy JSON now prefers the async Clipboard API (navigator.clipboard.writeText) in secure contexts, falling back to the legacy hidden-textarea + execCommand("copy") for insecure contexts.

React ↔ Angular bridge

  • Registers the <snapshot-content-viewer> directive via react2angular (components/index.js) and swaps it into restore-list.html in place of the Angular content block.

Styling

  • Ports snapshot-content.scss, scrollable.scss and the content-viewer parts of text.scss to Emotion (css), and adds shared styles/palette.ts / styles/icons.ts. Removes the now-unused SCSS imports from index.scss.

Removals

  • Deletes SnapshotContentCtrl.js, SnapshotModel.js, SnapshotModels.js and SnapshotCollectionService.js (and removes them from the controllers/models/services/collections indexes).
  • Drops the now-unused SnapshotService dependency from SnapshotListCtrl.

Tooling / build

  • Adds html-react-parser (article HTML → React) and @types/jest.
  • Widens the Jest testMatch to include .ts/.tsx and runs bddgen before npm run test.

Local dev / e2e

  • npm run dev:local now streams container logs to the terminal and surfaces logs/application.log on the host (via a new mountLogs option in startLocalStack), with the log mount kept out of the parallel e2e suite.
  • Updates the Copy JSON e2e steps to grant clipboard permissions and assert against the real clipboard.

How has this change been tested?

  • The existing Playwright/BDD end-to-end suite (npm run test) exercises snapshot content, the HTML/JSON toggle, Copy JSON, export links and the restore flow, all of which now run against the migrated React viewer.
  • Unit tests for the new snapshotContent model (npm run test:unit).
  • Manually via npm run dev:local:
    • Open a content id's versions and confirm the viewer renders furniture and article body, matching the previous layout.
    • Toggle Show JSON / Show TEXT and confirm the sliding animation and Copy JSON (including the "Copied!" label) behave as before.
    • Confirm the Restore action (when permitted) opens the modal and that keyboard navigation in the sidebar keeps the content in sync.

How can we measure success?

  • No regression in the existing e2e suite, and no change to the user-facing behaviour of the content viewer.
  • Continues the incremental migration pattern (React components bridged into AngularJS), leaving fewer Angular controllers/services to migrate in subsequent PRs.

@dominickendrick dominickendrick added the feature Departmental tracking: work on a new feature label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

// LinkButton renders an <a> (content-box) while Button renders a <button>
// (border-box), so the shared explicit height makes links taller. Force
// border-box to match the buttons.
const linkButtonBoxSizing = css({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should push this change back to Stand.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I can't see that they ship a CSS reset but it definitely applies here!

@dominickendrick
dominickendrick marked this pull request as ready for review September 10, 2026 15:20

@jonathonherbert jonathonherbert left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code looks fine, a few minor comments. I'll leave them here before testing manually.

Comment thread e2e-tests/images/run-dev-local.ts
// write succeeds and can be read back in the assertion below.
await page
.context()
.grantPermissions(["clipboard-read", "clipboard-write"]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread e2e-tests/steps/content-snapshot-content.steps.ts Outdated
* Render a snapshot's article body from its flattened list of elements,
* replacing the single `getHTMLContent` HTML string the legacy model produced.
*/
const ArticleBody: FunctionComponent<ArticleBodyProps> = ({ elements }) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ooh, do we have alignment on whether a single export at the bottom of the file, or inline exports export const ArticleBody, is preferable? Tools' usage of these styles vary. My e2e infra code almost exclusively uses inline exports. Don't have a super strong opinion on style, but it'd be nice to have consistency

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I feel like inline exports are clearer to read when scanning the code. Updated in 3b6f529

// LinkButton renders an <a> (content-box) while Button renders a <button>
// (border-box), so the shared explicit height makes links taller. Force
// border-box to match the buttons.
const linkButtonBoxSizing = css({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I can't see that they ship a CSS reset but it definitely applies here!

};

// Prefer the async Clipboard API (the modern, standard approach), falling back
// to a hidden textarea + `execCommand` for non-secure contexts where

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fallback shouldn't be necessary AFAICS — writeText has been around for a very long time

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove in 3b153c4

};
};

type RawListItem = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's brutal that we're defining types here, rather than integrating them from the writer upstream (which could I guess derive a great deal from flexible-model?), but I get that we have to start somewhere — wonder how we might share type information here, and who owns it 🤔

Comment thread public/javascripts/app/components/utils/mediator.ts

/** Subscribe to the request to show the rendered HTML view. */
const subscribeDisplayHtml = (callback: () => void): (() => void) => {
const handler = (): void => callback();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wonder why an intermediate handler is necessary here, mediator.subscribe(CHANNELS.displayHtml, callback); is identical other than the this context IIUC. Not blocking, as we expect this code to go

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Remove in 71faad0

@jonathonherbert

Copy link
Copy Markdown
Contributor

(Can't wait to tear out the event bus 😅)

@jonathonherbert

Copy link
Copy Markdown
Contributor

UI looks good! Two things I noticed: flicker when loading new snapshots (snapshots already loaded do not exhibit)
restorer

Super minor: content view loads when no snapshots are present

image

@dominickendrick dominickendrick self-assigned this Sep 15, 2026
Base automatically changed from dk-wsy-migrate-modal to main September 15, 2026 11:30
@dominickendrick
dominickendrick force-pushed the dk-wsy-migrate-content branch 2 times, most recently from 050302b to 5f7d3d3 Compare September 15, 2026 16:37
Hide the content panel immediately when the selected version changes and render furniture/panels only after content is available, eliminating visual jank for both mouse and keyboard navigation.
@dominickendrick

Copy link
Copy Markdown
Contributor Author

UI looks good! Two things I noticed: flicker when loading new snapshots (snapshots already loaded do not exhibit) restorer restorer

Super minor: content view loads when no snapshots are present

image

These were fixed in bf9f544 great spot !!

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

Labels

feature Departmental tracking: work on a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants