Skip to content

Refactor into pure vanilla ES modules, remove jQuery - #13

Merged
blokhin merged 26 commits into
mpds-io:masterfrom
luckylionheart:refactor-vanilla-es-modules
Nov 7, 2026
Merged

blokhin merged 26 commits into
mpds-io:masterfrom
luckylionheart:refactor-vanilla-es-modules

Conversation

@luckylionheart

Copy link
Copy Markdown
Contributor
  • split the global-namespace codebase into ES modules with explicit imports
    (core/, components/, app/), keeping window.wmgui for embeddability
  • remove jQuery, selectize, jquery.tablesorter; replace with vanilla
    multiselect and tablesort components sharing the same CSS class contracts
  • replace the Closure Compiler build with the dependency-aware Python
    bundler deploy/build_js.py (no Node, no Java); CI updated accordingly
  • add deploy/dev_api_proxy.py for local development against the MPDS API
  • update README (module map, compilation, local development)

- split the global-namespace codebase into ES modules with explicit imports
  (core/, components/, app/), keeping window.wmgui for embeddability
- remove jQuery, selectize, jquery.tablesorter; replace with vanilla
  multiselect and tablesort components sharing the same CSS class contracts
- replace the Closure Compiler build with the dependency-aware Python
  bundler deploy/build_js.py (no Node, no Java); CI updated accordingly
- add deploy/dev_api_proxy.py for local development against the MPDS API
- update README (module map, compilation, local development)
@luckylionheart

Copy link
Copy Markdown
Contributor Author

@blokhin
I tested with

python deploy/dev_api_proxy.py
python -m http.server 5555  

- Added a new parity testing framework to compare the refactored GUI against the previously deployed version.
- Introduced caching mechanism in the API proxy to store and replay responses for deterministic test runs.
- Created a new README.md in the parity directory to document usage and prerequisites for running tests.
- Developed a harness.js script to ensure consistent random value generation across test runs.
- Implemented run_parity.py to orchestrate the testing process, including setup, execution, and result reporting.
- Added HTML files for serving the old and new versions of the application during tests.
- Updated event handling in the application to prevent default navigation for certain elements.
- Ensured consistent DOM structure and behavior between the old and new implementations.
- Enhanced AJAX parameter serialization to match jQuery's behavior more closely.
- Improved visibility handling in the DOM manipulation functions to align with jQuery's show/hide behavior.
@luckylionheart

Copy link
Copy Markdown
Contributor Author

@blokhin
Please run the test python parity/run_parity.py

@luckylionheart

Copy link
Copy Markdown
Contributor Author

Drives the old deployed bundle (fetched from mpds-io.github.io/ermac) and the new one side-by-side in headless Chrome through 20 scenarios (every route: landing/search/inquiry modes/entry/phase/article/hierarchy/polyhedra/modals/plots, plus scripted clicks: advanced search popup, entry context, table sorting asc+desc, refinement expansion). Each scenario is compared on three layers: API request logs (must be identical), semantic DOM state (tables, row order, interpretation, history, visibility…), and pixels (0.1% threshold).

@blokhin blokhin self-assigned this Oct 23, 2026
- scenarios move to parity/scenarios.toml: a route + steps + probes per
  test, validated by --lint; the 20 original scenarios are migrated 1:1
  (the hard-coded harness.js acts become declarative steps)
- harness.js: in-page probe engine (text/value/class/visible/rect/focus/
  selection/storage/js ...), uncaught error capture, a synthetic step
  executor for the chrome-cli driver, localStorage presets
- run_parity.py: new socket-free Playwright driver (native input, files
  and API served by request interception, fake clock as virtual time,
  POST bodies in the API log, external requests logged), the original
  chrome-cli driver kept; new layers: probes, steps, errors, ext;
  per-scenario API mocks, storage presets, tooltips=false, ignore/xfail/
  skip, vacuous-probe and failed-old-step warnings, report.json
- remove the committed absolute symlinks parity/{labs,oauth,webassets}
  (they pointed into the author's home directory); gitignore them
- README: how to add a parity test, step/probe reference
…y loop

Guidance for coding agents that the README does not cover: hard rules
(no toolchain, window.wmgui and hash routes are public), where each kind
of change goes, the one-scope bundle and its import limits, CSS/request/
localStorage compatibility contracts, jQuery-migration traps seen in this
refactor, the parity verification loop, and MPDS API etiquette.
…rity suite

- remove the leftover ES5/jQuery global-namespace description that
  contradicted the ES-modules paragraph right after it
- replace 'No test suite' with the parity tests and how to run them
- link AGENTS.md; drop its now-obsolete stale-docs note
…r faithfully

The vanilla stand-ins are now ports of the vendored selectize 0.12 and
jquery.tablesorter 2.0, not reimplementations from their docs:

- multiselect: first suggestion auto-highlighted, refreshClasses state
  classes (has-options, full/not-full, input-active) at the selectize
  moments, no readOnly when full; suggestion mousedown selects and keeps
  the focus; chips selectable (click, ctrl, shift, Cmd/Ctrl+A) and deleted
  by Backspace/Delete; caret with ArrowLeft/Right; Enter deliberately not
  handled (commented out in the vendored copy: it bubbles to the search);
  autoGrow; explicit dropdown geometry; hideSelected; clearOptions keeps
  the selected items' options; deferred onFocus; blur on any document
  mousedown outside; debounced lookup with the last value; the original
  <select> mirrors the chips; selectize DOM attributes and classes
- tablesort: vendored parsers (chemical auto-detected, doi numeric, digit),
  ties keep the cached order, per-header click counters, ctrl multi-sort,
  update() re-reads rows without re-sorting, cancelSelection on mousedown,
  handlers replaced on re-init (they accumulated across searches)
- VIEW switches: all six lose all classes before the current two are set
- on(): jQuery delegated dispatch (deepest first, stopPropagation skips the
  outer handlers of the root, return false honored): one request per
  "Show" click again
- show()/hide()/toggle_display(): jQuery 3 semantics (remembered inline
  display, tag default display)
- ajax(): a network failure is no longer reported as an abort, so the
  "Connection to server is lost" notification shows again
- rankfilter sent as a number, like jQuery .data()
- wmgui.clean(): also strips embedded documents, on* attributes and
  javascript: URLs (except the app's own location.reload())
- autocomplete no longer imports app code (injected onHover hook)
- the OLD bundle is pinned to gh-pages commit 5978778 and verified by sha1
  on every run (the live demo serves the new bundle once deployed)
- pixel metric: a pixel differs when ANY channel exceeds the tolerance
- a scenario without API traffic is an ERROR, the run continues
- 429/5xx responses are never cached; POST cache keys include the body in
  both drivers (shared deploy/dev_api_proxy.py helpers); the proxy answers
  HEAD without a body and rejects a malformed Content-Length
- image requests left out of the ext layer (lazy-load distance depends on
  the browser build; image URLs are compared through the DOM)
- key steps hold the key 30 ms, so what keydown triggers runs before keyup
- new scenarios: sort_after_second_search, sort_ctrl_multi,
  ms_ctrl_a_select_all, ms_arrow_left_caret, ms_original_select_options;
  hy_expand no longer needs its ignore
- CI: parity on every pull request (component tags) and on dispatch (all),
  pinned Playwright/Chromium, cached API responses; deploy workflow moved
  to checkout@v4 / setup-python@v5
…erence and CI

AGENTS.md: the vendored selectize never handled Enter (it bubbles to the
search with the raw text); the DOM contracts of the ported components; the
jQuery dispatch, show/hide, ajax and sanitizer rules; the pinned reference
and the CI gate; host-dependent run time. parity/README.md: pinned
reference, pixel metric, ext layer, held keys, ERROR verdicts, CI.
Parity is relative (old vs new); `expect` adds absolute assertions on
probes, checked on each side: a plain value (equality) or an operator
table (equals, not, contains, not_contains, matches, gt, ge, lt, le).
Expected probes are linted and auto-added to the probe list; failures
are reported per side in the `expect` layer. Probes are now captured
once, `settle` ms after the last step.

New scenarios:
- search_ru_am_has_hits: #search/Ru-Am shows results (8 on real data)
- search_am_ru_binary_similar: typed "Am Ru binary" + Enter routes to
  #inquiry/elements=Am-Ru&classes=binary with the server's
  "similar data shown" notice
- 78 `qa` scenarios converted from a QA list of facet searches with
  expected result counts (page title / API estimated_count; zero-hit
  cases also expect the "No hits" notice). Old and new agree on all 78;
  75 differ from the QA list's numbers on the live API, so the tag is
  kept out of the PR CI set.
claude and others added 15 commits October 24, 2026 23:28
…ith expect_new

Root cause: the typed-text search copies the nested [prop, sign, value]
arrays of wmutils.guess() into the query, and the VIEW buttons copy
wmgui.search; both went through $.param, which writes numeric[0][]=...
keys the router cannot read. The deployed GUI therefore answers
"Unrecognized input" for "band gap > 3" and for the phases button of a
numeric search. The refactor's param() stringified arrays instead, which
fixed one condition by accident and broke two or more silently (no ';'
separator, so the router's length-3 filter dropped them).

- core/ajax.js: param() is now a faithful port of jQuery 3.7.1 $.param
  (buildParams: key[]= / key[i]= / key[name]=, null, functions, top-level
  {name, value} arrays), checked against the vendored jQuery.
- components/sliders.js: inquiry_param() writes a numeric array in the
  router's own prop,sign,value;... form (as serialize_numeric() does for
  the sliders); every #inquiry/#plot hash builder uses it. The route
  syntax is unchanged.
- parity: `expect_new` tables are checked on the NEW side only and make an
  xfail scenario FAIL when violated, so an approved change is pinned rather
  than merely tolerated; linted (requires xfail).
- scenarios: search_numeric_text (now xfail + expect_new),
  search_numeric_text_two_conditions, search_numeric_text_with_formula,
  numeric_view_phases_button (approved changes); numeric_route_two_conditions
  and numeric_hint_links (plain parity).
- AGENTS.md / parity/README.md: the inquiry_param() contract, the
  "simplified $.param" trap, approved changes via xfail + expect_new.
…r fallback

- The 78 qa scenarios' counts are re-recorded (2026-10-24; the QA list's
  own numbers were outdated and are kept in each desc) and checked loosely
  so growing data stays green: nonzero cases expect >= the recorded count,
  fuzzy-fallback cases also expect the "similar data shown" notice, zero
  cases expect 0 plus their notice ("No hits" / "Unrecognized property").
  Runs replay the recorded responses; --fresh-cache / --live re-check them.
- run_parity.py: falling back to the chrome-cli driver when playwright is
  missing is announced (synthetic input misses focus/blur regressions).
- Docs/CI comment: Playwright 1.50 ships Chromium 133, not 132.

Full run: 240 scenarios, PASS 236, XFAIL 4 (the approved numeric changes).
…ne panel only

- search.js open_sim_col(): the port scoped the property-name selectors to
  #sim_col ('#sim_col tr.busy_entry > td.c2'), but the selected busy_entry
  row/thumbnail is in the results, never in #sim_col. null.textContent
  threw inside the async function (an unhandled rejection), after the
  preloader was hidden and before anything was rendered: "See also" stayed
  empty for every property (P) entry, in the table and thumbnail views.
  Master's global selectors are restored, null-safe like jQuery's .text().
- events.js ESC: master's $('div.cross:visible').trigger('click') clicks
  EVERY visible cross; the port clicked only the first, so e.g. with an
  entry selected, the advanced search and the numeric box open, ESC left
  three panels open. All visible crosses are clicked again.
- New scenarios (tags context, sim, keyboard): sim_col_property_entry,
  sim_col_property_entry_thumbs, esc_closes_entry_columns,
  esc_closes_all_visible_panels, with absolute expectations. Against the
  unfixed code the first three fail (empty column + the unhandled
  rejection) and the last leaves 3 crosses visible.
- AGENTS.md: two migration traps (jQuery null-safety vs DOM throws in async
  code; jQuery set operations vs picking the first element).
…i fallback

- deploy/dev_api_proxy.py: redirects are no longer followed. An anonymous
  /download/p?...&fmt=pdf is a 302 to https://mpds.io/#junction; followed
  by urllib, the proxy served the mpds.io page (fragment lost) as the API
  response, its relative assets resolving to localhost:7070: broken HTML.
  Now the 3xx goes to the browser with its Location (rewritten to the proxy
  when it points back into the API), and the cache keeps the Location of a
  redirect (an extra header line for 3xx records only; older cache files
  read unchanged). fetch_remote() / local_location() are shared with the
  parity runner, whose request interception had the same problem.
- parity/run_parity.py: the playwright driver is the default and never
  falls back to chrome-cli by itself (that produced "FileNotFoundError:
  'google-chrome'" when pip had installed playwright for another
  interpreter). A missing playwright or a missing Playwright Chromium stops
  the run with the exact install commands for sys.executable;
  --driver chrome-cli finds Chrome/Chromium in PATH or /Applications.
  The storage-preset init script runs only in the GUI's own documents (it
  threw "localStorage ... Access is denied" pageerrors in opaque frames).
- README.md, parity/README.md: install with the same python3 that runs the
  suite, playwright pinned to 1.50.0.

Full run: 244 scenarios, PASS 240, XFAIL 4 (the approved numeric changes).
…rtial reports)

- Every Playwright side runs in a worker process with its own process group,
  streaming progress (launch, load, each step, each live API request,
  capture). No progress for --stall-timeout s (default 90, env
  PARITY_STALL_TIMEOUT) or more than --side-timeout s (default 600) kills
  the group (SIGTERM first, so Playwright's node driver also kills the
  browser, which lives in a group of its own; SIGKILL after 5 s). The
  scenario becomes ERROR "SideFrozen: <SIDE> side froze ..., last activity:
  <what>", the next one gets a fresh worker; 3 frozen scenarios in a row
  stop the run (a broken environment). --stall-timeout 0 runs in-process.
- Heartbeat lines every 15 s name the scenario, side and current activity,
  so a slow API is told apart from a freeze; stdout is line-buffered even
  when piped.
- Bounded inner waits: pending requests are awaited at most 60 s per side
  in total (it was up to 30 s at EVERY 50 ms virtual-time chunk), default
  Playwright timeouts are set, the browser is closed after the result is
  sent (a browser that does not close costs a worker, not the result), and
  the build has a 300 s timeout.
- report.json is rewritten after every scenario ("complete": false until
  the end); Ctrl-C kills the browsers at once, prints the partial summary
  and exits with 130.

Verified: an endless JS loop and a never-settling page call each end as
ERROR after the stall timeout with the run continuing; 3 in a row abort;
Ctrl-C exits at once with a partial report; no orphaned browsers. Full run:
244 scenarios, PASS 240, XFAIL 4, 556 s.
- parity/scenarios.json: the 244 scenarios as a bare list of objects;
  parity/config.json: {"defaults": {...}, "fixtures": [...]}. Converted
  mechanically from scenarios.toml (removed); the old TOML loader and the
  new JSON loader yield identical expanded scenarios and fixtures.
- JSON has no comments: an optional "comment" (string or list of strings)
  is allowed on any object and ignored; the TOML comments are kept that way,
  section banners as "comment": "section: ...".
- run_parity.py: no tomllib dependency any more (any Python 3); --config for
  an alternative config.json; the linter reports JSON syntax errors with
  line and column, duplicate keys (json would silently keep the last one,
  where TOML rejected them) and non-object entries.
- Docs (parity/README.md, README.md, AGENTS.md, harness.js) and the CI
  cache key follow; every JSON example in the README parses and lints.

Full run: 244 scenarios, PASS 240, XFAIL 4.
The fork's Actions default to a read-only GITHUB_TOKEN, so the
JamesIves deploy step failed with 'Permission denied to
github-actions[bot]' (403).
…e changes, a11y audit

- The OLD side now loads the stylesheet of the pinned gh-pages commit
  (OLD_CSS_URL / OLD_CSS_SHA1, --reference-css-url / --reference-css-sha1),
  url()s rewritten to webassets/. Before, both sides used the working
  tree's CSS, so a CSS change could never show up as a diff.
- ignore_attributes (default: role, tabindex, aria-*, inert, data-a11y-*)
  strips the accessibility markup from the DOM and probe values before the
  OLD-vs-NEW comparison; expect / expect_new still see the raw values.
- "approved": "<reason>" + expect_new: an approved change confined to the
  pinned probes (e.g. focus); those are not compared, every other layer
  is, and the scenario passes or fails normally. A whole-scenario xfail
  tolerates every layer and would hide unrelated regressions.
- harness.js: window.__a11y(), an audit of mouse-only controls, the Tab
  order, Tab stops outside an open dialog and controls a zoomed viewport
  cuts off.
…4.4)

WCAG 2.1.1 Keyboard:
- core/a11y.js: a declarative registry (A11Y_CONTROLS) gives the clickable
  div/span/li controls a role, label, state (aria-pressed/-selected/
  -checked/-expanded/-sort from the existing classes) and a Tab stop; a
  MutationObserver re-applies it to rendered content. Enter/Space call the
  element's click(), so the existing handlers serve the keyboard.
- Arrow keys in the periodic table (grid), the results, the galleries, the
  tab lists and the VIEW switches (roving tabindex).
- Dialogs: the page behind is inert (except the overlay, notifications,
  the sliders box, #userbox above the overlay, the body-level autocomplete
  suggestions, tooltips, the preloader); the dialog takes the focus and
  returns it to the opener on close; the sliders box returns it to its
  numeric control. wmgui.open_dialog() / wmgui.release_dialog() expose it.
- Entry columns: Enter on a row moves the focus into them, closing returns
  it to the row. A "Skip to content" button (hash routing: not a link),
  landmarks, labels on dialogs, buttons and slider handles; focus rings
  with :focus-visible only.

WCAG 1.4.4 Resize Text (150% and 200% of 1280x800):
- The periodic table and the hierarchy dialog are clamped to the window
  instead of a negative margin-left; the fixed side columns and the fixed
  dialogs scroll instead of cutting content off; at narrow widths the
  search area, grid columns, column titles and See also links make room;
  the hierarchy's vh line heights get an em floor.

Parity: 27 a11y scenarios (audits, zoom audits, keyboard contracts; xfail
against the deployed GUI, pinned by expect_new), a mouse-click autocomplete
scenario, and 48 scenarios whose only difference is the now focusable
clicked control, marked "approved" with the expected focus.
- Updated the parser definitions to use modern function syntax and improved formatting logic.
- Enhanced the `cell_text` function to directly access cell content based on row and column indices.
- Reworked the sorting mechanism to utilize a more efficient comparison strategy with locale-aware text handling.
- Implemented a new method for detecting parsers for each column dynamically.
- Improved event handling for header clicks, allowing for multi-column sorting with Ctrl key support.
- Cleaned up the code structure for better readability and maintainability.
- Adjusted the position calculation for the dropdown to account for the holder's margin, ensuring accurate placement.
- Updated comments for clarity regarding the positioning logic and the behavior of the dropdown during option clearing.
The vpat_fixes merge resolution dropped the 'steps' arrays of
login_click_error and login_network_error (fill the form, click the
trigger), so the expect_new focus probe read the field-focused state
of show_modal instead of the post-click state of the trigger.

Also re-apply the pixels=loose annotation of the three text-AA
rasterization scenarios (thumb_click_context, sim_col_property_entry,
sim_col_property_entry_thumbs), which the merge had reset to strict.
@blokhin

blokhin commented Nov 7, 2026

Copy link
Copy Markdown
Member

Merging preliminary, further QA pending

@blokhin
blokhin merged commit 5f738e2 into mpds-io:master Nov 7, 2026
1 check passed
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.

3 participants