Revamp UI: share ord-app's look-and-feel and stack; add landing page#210
Revamp UI: share ord-app's look-and-feel and stack; add landing page#210skearnes wants to merge 3 commits into
Conversation
Rebuild the search/browse SPA on the same foundation as the ord-app contribute/editor frontend so the two ORD frontends look and feel like one product, and shared code can be extracted later: - Mantine 7 with ord-app's theme and design tokens ported verbatim (marked to keep in sync until extracted to a shared package), Tabler icons, SCSS modules, and ports of ord-app's PaperButton, Pagination, DataTable, and NotFound components. - ord-schema-protobufjs bindings (same package and range as ord-app) replacing the google-protobuf ord-schema bindings; display components now type against the shared ord.I* interfaces. - wouter routing (replacing react-router-dom) and axios (replacing bare fetch), matching ord-app. - ketcher-react + ketcher-standalone from npm (lazy-loaded chunk) replacing the static Ketcher 2.5.1 iframe bundle; the manual "download Ketcher into app/public" dev step and the Docker copy step are gone (the legacy Flask editor keeps its own bundle). - New landing page: hero with natural-language search (routes to /ask), live reaction/dataset stats, action tiles, featured datasets and news curated via src/data/homeContent.ts, and a citation card. - All views rebuilt in the shared visual language: browse (DataTable), search filters (Accordion/SegmentedControl/RangeSlider), result cards, dataset view, reaction detail, selected set, NL search, About. - Drop Bootstrap, jQuery, DataTables, and Material Icons CDN tags. Fixes found during the migration: axios was transmitting protobufjs's whole shared pool buffer for /api/compound_svg (HTTP 500); peak wavelengths were formatted with Length units; yield precision displayed raw float32 noise. Verified against the in-process test backend: all routes render with no console errors; substructure search, dataset charts, and reaction detail work end-to-end. tsc -b, vite build, eslint, and prettier are green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // 200 = done, 202 = still running; anything else is an error. | ||
| const res = await api.get<Omit<SearchResult, 'data'>[]>('/fetch_query_result', { | ||
| params: { task_id: taskRef.current.taskId }, | ||
| validateStatus: status => status === 200 || status === 202, | ||
| }); |
There was a problem hiding this comment.
When /fetch_query_result returns an unexpected status such as 404 for an expired task id, axios rejects before the hook clears taskRef.current.taskId. The same query then keeps polling the dead task id instead of resetting the task state, so the search can stay wedged until the user changes the query.
| // 200 = done, 202 = still running; anything else is an error. | |
| const res = await api.get<Omit<SearchResult, 'data'>[]>('/fetch_query_result', { | |
| params: { task_id: taskRef.current.taskId }, | |
| validateStatus: status => status === 200 || status === 202, | |
| }); | |
| // 200 = done, 202 = still running; anything else is an error. | |
| const res = await api.get<Omit<SearchResult, 'data'>[]>('/fetch_query_result', { | |
| params: { task_id: taskRef.current.taskId }, | |
| validateStatus: () => true, | |
| }); | |
| if (res.status !== 200 && res.status !== 202) { | |
| const id = taskRef.current.taskId; | |
| taskRef.current.taskId = null; | |
| throw new Error(`Search task ${id} failed (HTTP ${res.status})`); | |
| } |
Findings from a screenshot sweep of every route, including modal, selection, empty, 404, and narrow-viewport states: - Reaction view: fix null guards that rendered blank rows (Workups target pH, stirring RPM, conditions pH accepted null via `!== undefined`); size Outcomes' internal headings below the section heading instead of equal to it; restyle the raw-data "<>" chips and CUSTOM link from dark gray blocks to flat bordered chips with the orange hover accent; soften the compound table header rule from black to the standard border color; scroll the full-record JSON inside its modal instead of past the viewport. - Search panel: match the add/draw button heights to their inputs. - Landing page: stack the action tiles before their subtitles truncate; place the featured-card count badge uniformly beneath the dataset name so long names never squeeze or split mid-word. Verified by re-screenshotting all routes and states; no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // 200 = done, 202 = still running; anything else is an error. | ||
| const res = await api.get<Omit<SearchResult, 'data'>[]>('/fetch_query_result', { | ||
| params: { task_id: taskRef.current.taskId }, | ||
| validateStatus: status => status === 200 || status === 202, |
There was a problem hiding this comment.
Clear failed tasks When
/fetch_query_result returns a terminal status such as 404 or 500 after an earlier 202, axios rejects here before taskRef.current.taskId is cleared. The query still has the last pending result, so the polling interval keeps firing with the same dead task id until the timeout or a URL change. Clear the stored task id on this rejected poll path before rethrowing so failed or expired tasks can reset instead of repeatedly polling stale state.
Replace the scroll-spy layout and legacy label/value sub-views with faithful ports of ord-app's ReactionPage in view-only mode, verified side-by-side against a live ord-app instance rendering the same reaction: - Page shell: breadcrumbs (Datasets / dataset / reaction), a Download Reaction action, a header card with the reaction id + copy button and the reaction scheme preview (input cards with name pills, molecule images, amounts/roles/limiting badges, arrow, outcome card with the Desired badge and yield), a Tabs/List SegmentedControl, and the section content card. - Nine section components ported from ord-app's ReactionView (Inputs, Outcomes, Conditions, Identifiers, Setup, Notes, Observations, Workups, Provenance) with its uppercase tab bar and mandatory-section asterisks, separated accordions, the shared identifiers/preview/role/details component grid, analysis cards, and KeyValueDisplay/RequiredOptionalFields primitives. Sections read the decoded ord.Reaction through context. - Molecule images come from the existing /api/compound_svg RDKit endpoint (react-query cached) in place of ord-app's Redux-fed Indigo previews; the ported ReactionComponentPreview states (loading / no-preview) carry over. - New shared pieces: Breadcrumbs, Counter, KeyValueDisplay, RequiredOptionalFields, typography helpers, and value/unit formatters (renderValuePrecisionUnit + the unit-symbol dictionary + ord-app's date format), all ported from ord-app and annotated as kept-in-sync copies. - Edit-only affordances (validation pill, View buttons/drawer, rename, Save as Template, Copy reaction image) are intentionally dropped; the server-rendered reaction_summary HTML is no longer used on this page. - Remove the superseded sub-views and now-unused legacy utils/styles (conditions/amount formatters, tabs/table/transition/vars partials). tsc -b, vite build, eslint, and prettier are green; every route re-verified headless with no console errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two follow-up commits since the PR opened:
The ported primitives (Breadcrumbs, Counter, KeyValueDisplay, RequiredOptionalFields, value/unit formatters, preview components) are annotated as keep-in-sync copies of their ord-app counterparts — natural candidates for the shared package extraction discussed in the PR description. 🤖 Generated with Claude Code |
| const res = await api.get<Omit<SearchResult, 'data'>[]>('/fetch_query_result', { | ||
| params: { task_id: taskRef.current.taskId }, | ||
| validateStatus: status => status === 200 || status === 202, | ||
| }); |
There was a problem hiding this comment.
Clear failed polls When
/fetch_query_result returns a terminal status like 404 or 500, this validateStatus makes axios reject before the hook clears taskRef.current.taskId. The last successful query data can still be pending, so the interval can keep polling the same dead task id instead of resetting and submitting a fresh task. Clear the stored task id on rejected poll responses before rethrowing, or handle non-200/202 responses after the request returns.
Summary
Rebuilds the search/browse SPA on the same foundation as the ord-app contribute/editor frontend, so the two ORD frontends look and feel like one product — and adds a proper landing page to help drive adoption.
Shared look-and-feel + stack (enables extracting shared code later)
theme.tsand CSS design tokens ported verbatim (annotated to keep in sync until extracted into a shared package), Tabler icons, SCSS modules, and direct ports of ord-app'sPaperButton,Pagination,DataTable(mantine-react-table wrapper), andNotFoundcomponents.ord-schema-protobufjs(same package + version range as ord-app) replaces the google-protobuford-schemabindings. Display components now type against the sharedord.I*interfaces — the same data currency as ord-app's converters — which is the key prerequisite for sharing reaction-display code.fetch), matching ord-app.ketcher-react+ketcher-standalonefrom npm (lazy-loaded chunk, fetched only when the draw modal opens) replace the frozen Ketcher 2.5.1 static iframe bundle. The manual "download Ketcher intoapp/public/" dev step and the Docker copy step are gone; the legacy Flask editor keeps its own bundle and is untouched.New landing page
Hero with the ORD logo and a natural-language search box that hands off to
/ask(LLM-backed name resolution), live reaction/dataset counts from/api/datasets, ord-app-style action tiles (Browse / Search / Contribute), featured datasets and a news timeline curated viaapp/src/data/homeContent.ts(featured list falls back to largest datasets automatically), an "Open by design" strip, and a copyable citation card.All views rebuilt in the shared language
Browse (ord-app's table look incl. orange row-hover), search filters (Accordion / SegmentedControl / RangeSlider), result cards, dataset view (header card + themed composition charts), reaction detail (sticky section nav + paper sections), selected set, NL search, and About.
Bugs found and fixed during the migration
/api/compound_svgbodies → HTTP 500 (fixed with an exact-size copy inencodeCompound).wavelengthStrformatter.± 4.800000190734863); now rounded.Testing
tsc -b,vite build,eslint, andprettierall green; pre-commit hooks pass.ORD_INTERFACE_TESTING=TRUE+ redis): every route screenshot at 1440px with zero console errors; substructure search (URL-driven state restoration included), dataset composition charts, reaction detail (protobuf decode + server-rendered summaries + compound SVGs), and the landing→/askflow all work.ComponentsKetcherEditor), file downloads, and clipboard copies — a manual pass is recommended.🤖 Generated with Claude Code
Greptile Summary
This PR rebuilds the search and browse SPA around the shared ORD app frontend stack. The main changes are:
Confidence Score: 4/5
This is close, but the failed search polling path should be fixed before merging.
app/src/hooks/useSearchTask.ts
Important Files Changed
Reviews (3): Last reviewed commit: "Rebuild the reaction page as a port of o..." | Re-trigger Greptile