Repository navigation
Refactor into pure vanilla ES modules, remove jQuery - #13
Merged
blokhin merged 26 commits intoNov 7, 2026
Merged
Conversation
- 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)
Contributor
Author
|
@blokhin |
- 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.
Contributor
Author
|
@blokhin |
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). |
- 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.
…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.
Member
|
Merging preliminary, further QA pending |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(core/, components/, app/), keeping window.wmgui for embeddability
multiselect and tablesort components sharing the same CSS class contracts
bundler deploy/build_js.py (no Node, no Java); CI updated accordingly