Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dab805f
Refactor into pure vanilla ES modules, remove jQuery
luckylionheart Oct 14, 2026
9a574be
feat(parity): implement automated comparative testing for refactored GUI
luckylionheart Oct 21, 2026
5567773
parity: declarative scenarios, Playwright driver, 130 narrow UX tests
claude Oct 23, 2026
44bc00a
Add AGENTS.md: contracts, bundler constraints, migration traps, parit…
claude Oct 23, 2026
d3df877
README: drop the pre-refactor architecture paragraph, document the pa…
claude Oct 23, 2026
5194713
Fix the UI regressions found by parity: port selectize and tablesorte…
claude Oct 23, 2026
d32c2b1
parity: pinned reference, corrected pixel metric, robust runs, CI gate
claude Oct 23, 2026
efd364e
Docs: correct the Enter semantics, document the contracts, pinned ref…
claude Oct 23, 2026
149789e
parity: absolute `expect` assertions; QA result-count scenarios
claude Oct 23, 2026
d529f10
Fix the numeric search URLs (approved change); pin approved changes w…
claude Oct 24, 2026
84af6a8
parity: re-record the qa counts from the live API; announce the drive…
claude Oct 24, 2026
e3fd304
Fix the empty "See also" column of property entries and ESC closing o…
claude Oct 25, 2026
9f0c47b
Dev proxy: pass redirects through; parity runner: no silent chrome-cl…
claude Oct 25, 2026
cae95e3
parity: a run cannot hang (watched worker per side, bounded waits, pa…
claude Oct 25, 2026
3b5ea6b
parity: scenarios in JSON (a list of objects) instead of TOML
claude Oct 25, 2026
670bb0a
CI: deploy the refactor-vanilla-es-modules branch on push
luckylionheart Oct 26, 2026
c407aa8
CI: grant the workflow write access to push gh-pages
luckylionheart Oct 26, 2026
3e373a9
parity: pin the OLD stylesheet, ignore a11y attributes, approved prob…
claude Oct 26, 2026
cefcce2
Accessibility: keyboard operation, dialog focus, zoom (VPAT 2.1.1, 1.…
claude Oct 26, 2026
c792d31
docs: accessibility contracts, the a11y audit, pinned CSS, approved p…
claude Oct 26, 2026
5b7ca05
Build GUI from vpat_fixes
blokhin Oct 26, 2026
dad5ea5
Refactor Ermac table sorter: improve parser handling and sorting logic
luckylionheart Oct 27, 2026
885c16f
Fix dropdown positioning in multiselect component
luckylionheart Oct 28, 2026
26d381b
Resolve merge conflicts in PR #1
luckylionheart Nov 4, 2026
fe2895f
parity: restore the login scenario steps lost in the VPAT merge
luckylionheart Nov 5, 2026
675d82f
Implement code changes to enhance functionality and improve performance
luckylionheart Nov 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions .github/workflows/deploy-to-gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,35 @@ on:
push:
branches:
- master
- refactor-vanilla-es-modules
- vpat_fixes
workflow_dispatch:

# NB the fork's Actions default is read-only: the deploy step needs
# write access to push the gh-pages branch
permissions:
contents: write

jobs:
build-then-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup JAVA
uses: actions/setup-java@v3
- name: Setup Python
uses: actions/setup-python@v5
with:
distribution: 'zulu'
java-version: '17'
check-latest: true
python-version: '3.x'

- name: Run per se
run: bash deploy/build_js.sh
run: python3 deploy/build_js.py
shell: bash

- name: Save files
run: |
mkdir dist
cp ermac.min.js src_js/third_party/jquery.min.js dist
cp ermac.min.js dist
cp -r webassets oauth crystvis labs dist
cp example_prod.html dist/index.html

Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Parity with the deployed GUI

# The refactored GUI must behave exactly like the pinned deployed bundle
# (see parity/README.md). Pull requests run the scenarios of the components
# most at risk; the full battery runs on demand. Recorded MPDS API responses
# are cached between runs to keep the load on api.mpds.io minimal.

on:
pull_request:
workflow_dispatch:
inputs:
only:
description: 'scenario names or globs (empty = all)'
required: false
default: ''

jobs:
parity:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Playwright and Chromium
run: |
# NB pinned: the browser build affects rendering and lazy loading;
# 1.50 = Chromium 133, the build the suite is validated with
pip install playwright==1.50.0 pillow
python -m playwright install --with-deps chromium

- name: Restore recorded API responses
uses: actions/cache@v4
with:
path: parity/out
key: parity-api-${{ hashFiles('parity/scenarios.json', 'parity/config.json') }}-${{ github.run_id }}
restore-keys: |
parity-api-${{ hashFiles('parity/scenarios.json', 'parity/config.json') }}-
parity-api-

- name: Run the parity suite
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
if [ -n "${{ github.event.inputs.only }}" ]; then
python3 parity/run_parity.py --only '${{ github.event.inputs.only }}'
else
python3 parity/run_parity.py
fi
else
python3 parity/run_parity.py --tag legacy,multiselect,sort,controls,mock,keyboard
fi

- name: Upload the evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: parity-out
path: |
parity/out/report.json
parity/out/*/diff.txt
parity/out/*/*-capture.json
parity/out/*/*.png
if-no-files-found: ignore
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ labs/pd3d/data
package.json
package-lock.json
/node_modules

# parity testing
parity/reference/
parity/out/
__pycache__/
# created at runtime by the chrome-cli driver (app_folder is /parity)
parity/webassets
parity/labs
parity/oauth
parity/crystvis
parity/ermac.min.js

.venv
280 changes: 280 additions & 0 deletions AGENTS.md

Large diffs are not rendered by default.

73 changes: 44 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,57 +20,64 @@ cd ermac
# then run your static web-server and open it in a web-browser
```

**NB local development.** When served from a "localhost" origin, the GUI switches to development mode and expects the MPDS API at `http://localhost:7070` (see `src_js/core/settings.js`). There is no local backend in this repository — the data stays at the MPDS server — so run the bundled reverse proxy to the real API in a second terminal:

## Technical details
```
python3 deploy/dev_api_proxy.py # 127.0.0.1:7070 -> https://api.mpds.io/v0
```

Ermac is just a thin browser client (with a little fat). From whatever physical location, it talks to the MPDS platform servers at the `api.mpds.io` domain. Beware, Ermac employs the old-style JQuery-fashioned ES5 JavaScript (see `src_js` folder). The `Node` and `npm` are intentionally **NOT** used. Several external dependencies are supplied simply along with the codebase in `third_party` and `src_js/third_party` folders. For future, we consider re-implementation of this codebase in the modular TypeScript framework. Please [contact us](mailto:hello@tilde.pro) if you'd like to know more or help.
Without it, the client data request fails and the GUI won't finish initializing (`Failed to load GUI`). Alternatively, serve the GUI from any non-"localhost" origin (e.g. `http://127.0.0.1:5555` or a LAN address): the GUI then talks to `https://api.mpds.io/v0` directly, no proxy needed.


## Compilation
## Technical details

Compilation into the production bundle `ermac.min.js` from `src_js` is done via the Google Closure Compiler supplied in `third_party/jscomp` folder, see `deploy/build_js.sh` script. For that you need a Java Runtime Environment (JRE), i.e. a command `java -version` should _not_ produce an error in your terminal. On a typical Unix, such as Debian, JRE is installed e.g. like this:
Ermac is just a thin browser client (with a little fat). From whatever physical location, it talks to the MPDS platform servers at the `api.mpds.io` domain. Ermac is written in modular **pure vanilla JavaScript (ES modules)**, without any framework. The `Node` and `npm` are intentionally **NOT** used. The few remaining external dependencies are supplied simply along with the codebase in `src_js/third_party` (noUiSlider, wNumb, the MPDS NLP parser, and darkmode.js — all framework-free), with the jQuery era of this codebase being over. Please [contact us](mailto:hello@tilde.pro) if you'd like to know more or help.

```
apt-get -y update && apt-get -y upgrade
apt-get install default-jre
java -version
```

Given your JRE works, compilation is done as follows:
## Compilation

Compilation into the production bundle `ermac.min.js` from the ES modules in `src_js` is done via the dependency-aware script `deploy/build_js.py`, which requires only a Python 3 interpreter (still no Node). It concatenates the vendored libraries and the application modules in topological order, stripping the `import`/`export` statements:

```
bash deploy/build_js.sh
python3 deploy/build_js.py
```

The resulted file `ermac.min.js` is to be included into your webpage (see `example_prod.html`). Alternatively, see [Ermac demo](https://mpds-io.github.io/ermac) which is just a repository branch `gh-pages` compiled by GitHub action.


## Code architecture

The entire app lives in a global namespace object `window.wmgui`. There is no module system, and the code is intentionally ES5 jQuery-style (some files use ES6+ features and are compiled with `ECMASCRIPT_NEXT`).
The entire app lives in the global state object `window.wmgui` (kept for embeddability) while being structured as ES modules with explicit dependencies. In the **development mode**, the code is served directly from the `src_js/app/run.js` ES module entry (see `example_dev.html`). In the **production mode**, the code is compiled into a single static bundle `ermac.min.js` (see `example_prod.html`).

**Script load order** (defined in `example_dev.html` and mirrored in `deploy/build_js.sh`):
**Module map** (entry: `src_js/app/run.js`):

| File | Role |
| Module | Role |
|---|---|
| `wmsettings.js` | All global state, constants, API endpoint URLs, edition configs, facet definitions |
| `wmcore.js` | Utility functions, DOM helpers, AJAX wrappers, selectize read/write helpers |
| `sliders.js` | Numeric range slider UI (noUiSlider-based) |
| `ptable.js` | Interactive periodic table widget |
| `router.js` | Hash-based routing — `url__<route>()` functions handle `#search/`, `#inquiry/`, `#phase/`, `#polyhedra/`, etc. |
| `events.js` | DOM event listeners and `register_events()` |
| `markup.js` | HTML generation and `register_html()` (injects the full UI into `<body>`) |
| `main_logic.js` | Core search logic: `request_search()`, display/render functions, `wmgui.notify()`, `wmgui.aug_search_cmd()` |
| `startup.js` | `satisfy_requirements()` — initializes selectize instances, loads `wmdata.json`, wires autocomplete, triggers initial route |
| `run.js` | Entry point — calls `assign_edition()`, `register_html()`, `satisfy_requirements()`, and `register_events()` |
| `core/dom.js` | Vanilla DOM helpers (`$`, `$$`, show/hide, delegated events, offsets) |
| `core/ajax.js` | Abortable promise-based XHR wrapper, URL serialization (`param`), `is_empty` |
| `core/settings.js` | All global state (`window.wmgui`), constants, API endpoint URLs, edition configs |
| `core/utils.js` | Base-type extensions, `wmgui.clean`, `loadCSS`, domain data, interpretation HTML, random query generators |
| `core/a11y.js` | Keyboard accessibility: a declarative registry giving the clickable `div`/`span`/`li` controls roles, labels, states and Tab stops, Enter/Space activation, arrow keys in grouped widgets, focus management of dialogs (`wmgui.open_dialog()` / `wmgui.release_dialog()`) |
| `components/multiselect.js` | Vanilla multi-select input (replaces selectize.js, same CSS classes) |
| `components/tablesort.js` | Vanilla table sorting (replaces jquery.tablesorter, same CSS classes) |
| `components/autocomplete.js` | Simple autocompletion (converted from the vendored spin-off) |
| `components/sliders.js` | Numeric range slider UI (noUiSlider-based) |
| `app/ptable.js` | Interactive periodic table widget |
| `app/router.js` | Hash-based routing — an explicit registry of `url__<route>()` handlers for `#search/`, `#inquiry/`, `#phase/`, `#polyhedra/`, etc. |
| `app/events.js` | DOM event listeners and `register_events()` |
| `app/markup.js` | HTML generation and `register_html()` (injects the full UI into `<body>`) |
| `app/search.js` | Core search logic: `request_search()`, display/render functions, `wmgui.notify()`, `wmgui.aug_search_cmd()`, user session |
| `app/startup.js` | `satisfy_requirements()` — initializes multiselects, loads client data, wires autocomplete, triggers initial route |
| `app/run.js` | Entry point — calls `assign_edition()`, `register_html()`, `satisfy_requirements()`, `register_events()` and `install_a11y()` |

**Key concepts:**

- `wmgui.search` — the live search state object, synced with URL hash parameters
- `wmgui.search_type` — `0` = entries, `1` = phases, `2` = articles
- `wmgui.facets` — the 15 search categories (formulae, props, elements, classes, lattices, sgs, protos, authors, years, codens, doi, aeatoms, aetypes, geos, orgs)
- `wmdata.json` — client-side data (properties list, journal CODENs, property hierarchy) fetched at startup from `wmgui.client_data_addr`
- `wmdata` — client-side data (properties list, journal CODENs, property hierarchy) fetched at startup from `wmgui.wmdata_endpoint`
- `wmgui.editions` — multi-tenant config keyed by edition ID (0 = MPDS, 1 = ASM, 10/15/16 = Ermac variants); the active edition is detected from the current hostname
- Accessibility — every control is reachable and operable with the keyboard (Tab, Enter/Space, arrow keys in the periodic table, the results, the tabs and the VIEW switches; ESC closes panels), dialogs keep the focus inside and return it on close, and the layout stays usable at 200% zoom. A host page that adds its own dialog can use `wmgui.open_dialog(box, initial_selector)` / `wmgui.release_dialog()`

**Embedded sub-apps** (loaded in iframes):

Expand All @@ -79,14 +86,22 @@ The entire app lives in a global namespace object `window.wmgui`. There is no mo
- `labs/pd3d/` — 3D phase diagram viewer (Three.js-based)
- `labs/view-phonons/` — phonon visualiser

**Third-party libraries** (vendored in `src_js/third_party/` and `third_party/`):
**Third-party libraries** (all framework-free, vendored in `src_js/third_party/`):

- jQuery, selectize.js, noUiSlider, wNumb, autocomplete.js, darkmode.js, jquery.tablesorter
- noUiSlider, wNumb, darkmode.js
- `optimade_mpds_nlp.js` — NLP parser that converts free-text queries to structured facet objects

### No test suite
### Parity tests

The `parity/` suite checks that the GUI behaves exactly like the deployed version at [mpds-io.github.io/ermac](https://mpds-io.github.io/ermac): every scenario (a route, optional clicks and keystrokes, and the observations to compare) runs against the old and the new bundle in a headless browser, comparing API requests, DOM state, focus, JS errors and screenshots. Scenarios are declared in `parity/scenarios.json` (a list of scenario objects; defaults and fixtures in `parity/config.json`); see [`parity/README.md`](parity/README.md) for running them and adding new ones.

```
# NB one interpreter throughout: install with the python3 that runs the suite
python3 -m pip install playwright==1.50.0 pillow && python3 -m playwright install chromium
python3 parity/run_parity.py
```

There are no automated tests. Verify changes by loading `example_dev.html` in a browser and exercising the search and visualisation features manually.
Contributors and coding agents: see [`AGENTS.md`](AGENTS.md) for the compatibility contracts and pitfalls of changing this codebase.


## License
Expand Down
Loading
Loading