Performance and accessibility checks¶
The 0.8 qualification (issue #68) asks for measured performance and keyboard-accessible workflows. Its budgets are frozen by the maintainer from measurements like the one below, before anything is optimised. This page describes the tools that produce those measurements, and records the first.
The corpus¶
tests/data/corpus/ holds a versioned, public-safe set of drawings as
.vsk documents. manifest.json records each case’s SHA-256 and what it
holds, so a measurement names exactly what it measured:
Case |
Kind |
Atoms |
Species |
Reactions |
|---|---|---|---|---|
|
small |
3 |
1 |
0 |
|
small |
13 |
5 |
0 |
|
small |
12 |
3 |
1 |
|
small |
40 |
6 |
3 |
|
large |
201 |
1 |
0 |
|
large |
1,020 |
31 |
30 |
|
large |
2,000 |
1 |
0 |
|
crowded |
534 |
60 |
0 |
|
crowded |
2,685 |
150 |
0 |
The small cases are the manual’s example documents; scripts/make_corpus.py
generates the others deterministically and checks the files
(--check). Changing a case means a new corpus version, and measurements of
different versions are not compared. The test suite checks the manifest and
reopens every case through .vsk and QVF with the same species and
reactions, so work on speed cannot quietly change graph or stereo fidelity.
The harness¶
python scripts/benchmark.py --output perf.json
python scripts/benchmark.py --case large-scheme-30 --repeat 10
For each case it times loading, deriving species and reactions, cleanup,
Tidy scheme (when there are reactions), SVG, PNG and PDF rendering and QVF
export: once cold and --repeat times warm (5 by default), keeping every
observation, with the peak memory Python allocated in a separate run
(tracemalloc, which leaves out what RDKit allocates in C++). An operation
that does not apply or whose optional part is not installed is recorded as
skipped, a deliberate refusal as refused, and anything else as a failure
with a nonzero exit. The record names the operating system, architecture,
processor model, logical CPUs, memory, Python, RDKit, vibe-sketch and corpus
versions, never the machine’s name.
First measurement (2026-10-22)¶
Apple M4 Pro, 12 logical CPUs, 24 GiB, macOS (Darwin 27.0), Python 3.14.8, RDKit 2026.09.1, vibe-sketch 0.3.0 (main), corpus version 1; warm medians of three runs, in seconds:
Case |
Load |
Derive |
Cleanup |
Tidy |
SVG |
PNG |
QVF |
|
|---|---|---|---|---|---|---|---|---|
small-ethanol |
0.001 |
0.001 |
0.001 |
– |
0.000 |
0.004 |
0.000 |
0.006 |
small-stereochemistry |
0.001 |
0.004 |
0.010 |
– |
0.001 |
0.033 |
0.004 |
0.067 |
small-mechanism |
0.000 |
0.002 |
0.003 |
0.029 |
0.002 |
0.010 |
0.002 |
0.014 |
small-three-step |
0.001 |
0.010 |
0.033 |
0.046 |
0.002 |
0.072 |
0.026 |
0.033 |
large-peptide |
0.002 |
0.023 |
0.216 |
– |
0.019 |
0.148 |
0.037 |
0.048 |
large-scheme-30 |
0.007 |
0.107 |
0.463 |
0.877 |
0.070 |
1.459 |
0.160 |
0.383 |
large-chain-2000 |
0.012 |
0.228 |
20.928 |
– |
0.153 |
refused |
refused |
0.933 |
crowded-numbered-60 |
0.005 |
0.143 |
0.394 |
– |
0.036 |
0.405 |
0.108 |
0.321 |
crowded-grid-150 |
0.021 |
0.221 |
1.204 |
– |
0.138 |
1.062 |
0.217 |
1.142 |
What it showed:
Everything but cleanup of the 2,000-atom chain stayed near or below 1.5 s. That cleanup took about 21 s and peaked at about 217 MB of Python memory.
The chain’s PNG and PDF are refused: the figure would be wider than the 14,400 pt publication limit. That refusal is by design; SVG and QVF work.
The maintainer judged that cleanup over budget. Profiling showed that most of it went to three whole-molecule layouts tried one after another, whose cost grows faster than the molecule. Per candidate, in seconds (rdkit / mimic / coordgen), they took 0.10 / 0.08 / 0.13 at 500 atoms, 0.72 / 0.47 / 0.60 at 1,000 and 5.2 / 1.8 / 2.1 at 2,000. The overlap count built an n × n matrix, about 130 MB at 2,000 atoms. Cleanup now:
lays out a molecule of more than 500 atoms with the distance-matrix “mimic” candidate alone, which is the fastest at that size and follows the drawing most closely (on the chain all three tie, and mimic was the one chosen);
leaves a molecule of more than 3,000 atoms as drawn, saying why (mimic alone takes 3.5 s at 3,000 atoms and 10 s at 5,000);
counts overlaps row by row, in memory linear in the atoms.
After the cleanup limits (2026-10-22)¶
The same machine and corpus, warm medians of three runs, in seconds:
Case |
Load |
Derive |
Cleanup |
Tidy |
SVG |
PNG |
QVF |
|
|---|---|---|---|---|---|---|---|---|
small-ethanol |
0.000 |
0.001 |
0.001 |
– |
0.000 |
0.001 |
0.000 |
0.004 |
small-stereochemistry |
0.001 |
0.003 |
0.003 |
– |
0.001 |
0.010 |
0.002 |
0.017 |
small-mechanism |
0.001 |
0.002 |
0.003 |
0.010 |
0.001 |
0.005 |
0.001 |
0.011 |
small-three-step |
0.001 |
0.004 |
0.010 |
0.026 |
0.002 |
0.029 |
0.008 |
0.021 |
large-peptide |
0.002 |
0.005 |
0.110 |
– |
0.009 |
0.080 |
0.015 |
0.028 |
large-scheme-30 |
0.007 |
0.058 |
0.221 |
0.388 |
0.028 |
0.708 |
0.082 |
0.164 |
large-chain-2000 |
0.010 |
0.113 |
2.535 |
– |
0.080 |
refused |
refused |
0.400 |
crowded-numbered-60 |
0.004 |
0.047 |
0.130 |
– |
0.018 |
0.189 |
0.043 |
0.152 |
crowded-grid-150 |
0.017 |
0.113 |
0.592 |
– |
0.067 |
0.499 |
0.113 |
0.442 |
The chain’s cleanup now takes 2.5 s instead of about 21 s, and its peak Python memory is 64 MB instead of 217 MB. It still cleans, with the same result, and the smaller cases are unchanged. The 50-atom cleanup target of #18 is met by a wide margin. One run on one machine is not a budget: the maintainer freezes budgets from records like these.
Keyboard and screen readers¶
The browser shell’s tests check, in a real browser:
the WCAG 2.2 A and AA rules axe-core can test, on the shell and its panels in several states (
tests/test_browser_accessibility.py);menus, the tool bar and the panel by keyboard alone, with focus kept where the user was when the panel updates;
that every control the Tab key reaches shows a 2 px focus ring;
that information is announced politely and fades, while an error interrupts screen readers (
role="alert") and stays until the next message or a click, so nobody has to read it in time;a whole session by keyboard alone: import a structure, generate its 3D structure, submit a calculation and cancel it, save with Ctrl+S, export through the Export menu and open a file, each control reached with Tab from where the last one left the focus;
that Tab and Shift+Tab both leave the canvas, in either palette.
Canvas limitations¶
The drawing canvas is Ketcher’s (EPAM). Where it falls short, the shell offers another way:
Limitation |
What to use instead |
|---|---|
Atoms and bonds are drawn with a pointer; there is no way to place them by keyboard. |
Import ▾ → SMILES…, Name… or File…, all by keyboard; or the SDK, CLI, REST API and MCP tools. |
A screen reader cannot read the drawing itself. |
The Properties panel lists each species with its formula, compound number, multiplicity and name, and each reaction with its sides; Skip to properties and inputs jumps there. |
Ketcher’s palettes cycle Tab among themselves, and its clipboard area takes the focus back on Shift+Tab, which would trap a keyboard user. |
The shell moves the focus itself: Tab and Shift+Tab pass through the canvas’s controls and out of it. |
Ketcher’s palettes add about 47 Tab stops. |
The compact palette ( |
Ketcher’s canvas is outside the axe-core audit, which cannot judge an upstream drawing surface. |
The shell around it, the panels and the menus are audited. |
A manual review with a screen reader is still to come, as #68 asks.