Architecture
ReShiki owns its editable document and chemistry operations in Rust. The pinned Rust InChI kernel runs in an isolated worker mode of the same executable, as do 3D geometry, chemical names and several platform services. Python/RDKit is retained only as an optional development reference.
flowchart LR UI[Iced controls and canvas] --> App[Messages, history, selection] App <--> Doc[Rust document] Doc --> Scene[Vector scene] Scene --> Canvas[Iced geometry] Scene --> SVG[SVG export] App --> Local[ChemistryEngine / LocalEngine] Local --> Rust[Rust chemistry and drawing interchange] Rust <--> InChI[Same executable in isolated InChI worker mode]Worker modes
Section titled “Worker modes”A worker mode relaunches the same executable for one bounded job. main checks the first argument against the table in src/workers.rs (reshiki::workers::run) before graphics, Office registration or Tokio start, so a deadline or memory failure ends only the worker.
| First argument | Platforms | Started by | Job |
|---|---|---|---|
--inchi-worker |
All | crates/chemistry/src/inchi/generator.rs |
InChI generation and reconstruction |
--geometry-worker |
All | crates/io/src/geometry/client.rs |
3D geometry with the bounded MMFF/UFF and ETKDG solver |
--naming-worker |
All | src/naming/client.rs |
One name parse or one name generation |
--emf-worker |
Windows | src/metafile.rs |
Plays an imported EMF file into its PNG preview |
--emf-file-worker, --emf-office-worker |
Windows | native/windows/src/metafile_worker.rs, for retained EMF pictures |
Records an EMF figure file or an Office object |
--libreoffice-preview, --libreoffice-clipboard, --libreoffice-copy |
All | The optional LibreOffice extension (src/libreoffice.rs) |
Preview, clipboard read and clipboard write |
--clipboard-worker |
macOS, Linux | src/clipboard.rs, src/clipboard/linux.rs |
One clipboard read or write |
--print-worker |
macOS | src/printing.rs |
Print dialog and printing of a bounded PDF snapshot |
The flags are distinct, so the order of the table cannot change which worker runs. LibreOffice owns its own flags and is checked when the table has no match. Runtime safety records the limits of the naming and EMF workers.
Crates and layering
Section titled “Crates and layering”The library is split into workspace crates. Each crate depends only on the crates before it:
reshiki-chemistry(crates/chemistry/): the chemistry core, without GUI dependencies.reshiki-model(crates/model/): document, scene, styles, editing, pictures, storage and themes, plus the drawing-to-molecule and abbreviation adapters inchemistry/.reshiki-io(crates/io/): chemistry engine, drawing interchange, export, recovery, document styles and the template library, plus the CDXML, MOL, reaction and cleanup adapters inchemistry/.reshiki-agent(crates/agent/): the assistant’s GUI-free proposal schema, layout, composition, sketch diagrams, review and canvas inspection and rendering. It also hosts the experimental operation layerops:ToolHost,HeadlessHost, the session document store, budgets, cancellation and the neutral tool catalog, andaccess, the folder grants of the agent file tools. Its transports arereshiki-mcpand the root crate’sreshiki::cli, started byreshiki --mcpandreshiki --cli(src/launch.rs).reshiki-mcp(crates/mcp/): the experimental MCP transport behindreshiki --mcp: bounded stdio line framing, admission, backpressure and cancellation tracking, the rmcp protocol layer, the bridge to aToolHostand the content-free stderr log. It uses no filesystem APIs.reshikilibrary (src/lib.rs): a facade that re-exports the moved modules at their existingreshiki::<module>paths, plus the Codex assistant client and its preferences, clipboard, printing, updates, hotkeys, Office and accessibility services, chemical names (naming), EMF import (metafile), the worker modes (workers) and the experimentalreshiki --clicommands.reshikiexecutable (src/main.rs,src/launch.rs,src/session.rs,src/app/,src/canvas/, andsrc/desktop/on Windows and Linux): the Iced application, and the headless--mcpand--climodes selected by the first argument.
Leaf crates depend on no layer crate; only reshiki-windows uses another leaf, reshiki-metafile. The crates above use them:
reshiki-metafile(crates/metafile/): bounded EMF framing checks and the physical picture size, plus the picture size limits for every format.reshiki-modelandnative/windowsuse it.reshiki-process(native/process/): platform limits for the naming worker’s child process. All of its platform FFI stays in this crate.reshiki-process-heap(crates/process-heap/): the bounded allocator of worker processes and the adaptive calculation resource policy.reshiki-depict-math(crates/depict-math/): the paired math call for the pinned depiction ABI, used byreshiki-chemistry.reshiki-geometry(native/geometry/): the bounded MMFF/UFF and ETKDG geometry solver, used byreshiki-io.reshiki-macos,reshiki-windowsandreshiki-linux(native/macos/,native/windows/,native/linux/): platform services such as the clipboard, printing, document handoff and Office integration, as each platform supports them.
reshiki::chemistry combines the core with both adapter layers, so existing imports keep working. Crate boundaries enforce the layering: a lower crate cannot import a higher one. The layer crates are workspace default members, so cargo test and the pre-commit checks cover them. The leaf crates are workspace members but not default members: cargo test --workspace, as in the native checks and CI, also runs their tests. Release builds still produce exactly one executable with cargo build --release --bin reshiki; build.rs stays in the root package. The agent API adds no executable: reshiki --mcp and reshiki --cli are modes of the same binary, like the chemistry workers.
Chemical naming comes from the external nazuchem repository, at the nazuchem revision pinned in Cargo.toml. ReShiki uses four of its five crates as dependencies, outside the workspace: nazuchem-ir holds shared naming types, nazuchem-kernel supplies perception and canonical identity, and nazuchem-n2s and nazuchem-s2n provide the two engines. The facade crate nazuchem is unused by ReShiki. The n2s engine is a Rust port of OPSIN by Daniel Lowe under MIT, tracking upstream main after 2.9.0 (commit 42e87ac6; phane nomenclature included). Its porting ledger and provenance and crate-local license texts live in that repository. ReShiki retains the host budgets, worker transport, application integration, catalog and naming regression harness.
Modules
Section titled “Modules”| File | Responsibility |
|---|---|
crates/model/src/document.rs |
Atoms, bonds, text, arrows, stable object IDs, validation, undo/redo snapshots |
crates/model/src/scene.rs |
Toolkit-independent lines, polygons and labels; scene/svg.rs writes SVG |
crates/model/src/scene/bond_pass.rs |
Two-stage bond preparation: owned inputs, then a borrowed pass |
crates/model/src/scene/projected_bonds.rs |
Double-bond rails in their retained ring face; the per-scene face index |
crates/model/src/scene/attachment_ink.rs |
Visible-ink hit tests for new mechanism-arrow targets |
crates/model/src/depth_appearance.rs |
Depth paint scopes: RGB fade, rear-opacity alpha, RGB materialization |
crates/model/src/rear_opacity.rs, rear_opacity/ |
Rear-opacity paint, view visibility, clipping and alpha-resolved ink |
crates/model/src/generic.rs, crates/model/src/generic/ |
Generic definitions, validation, edit guards and bounded finite expansion |
crates/model/src/reaction_mapping.rs, reaction_mapping/ |
Atom maps and the bounded Auto-map graph search |
crates/model/src/molecule_names.rs |
Linked name captions and the canonical identity that naming shares |
crates/model/src/arrow_anchors.rs |
Mechanism-arrow target links and their reconciliation |
crates/model/src/graphics/path.rs |
Pen path nodes and their independent Bézier controls |
crates/model/src/editing/reference.rs |
Reference-edge alignment and single-bond stretch |
crates/model/src/joining.rs, crates/model/src/joining/ |
Move & attach, multi-atom merge, in-place coordination and the Join route plan |
src/canvas.rs |
Canvas module root: canvas types, camera, event and draw dispatch |
src/canvas/hit.rs |
Hit testing for pointers and regions, and drawn-bond attachment targets |
src/canvas/snapping.rs |
Where gestures land: move deltas, smart guides, arrow ends, ring anchors |
src/canvas/input/ |
Pointer and keyboard events: presses, motion and per-gesture releases |
src/canvas/paper/ |
Paper drawing: background, active-gesture preview, document and overlays |
src/canvas/previews.rs |
Thumbnail, inspector, palette and proposal canvas programs |
src/app.rs |
App state, messages, lifecycle, update entry points and the root view |
src/app/gates.rs |
Ordered modal gates that run before message dispatch |
src/app/dispatch.rs |
Message dispatch table: one handler per message variant |
src/app/canvas_edit/ |
Canvas gesture dispatch, direct manipulation, placement and clicks |
src/app/assistant.rs, src/app/assistant/ |
Assistant actions, request lifecycle, attachments, drafts and panel view |
crates/agent/src/ |
Assistant proposals and canvas tools; the experimental ops operation layer |
src/launch.rs |
First-argument --mcp/--cli dispatch, heap ceiling, grants and shutdown |
src/workers.rs |
Worker-mode flags and their dispatch, before the GUI starts |
src/session.rs |
Startup arguments and host edit sessions (Office, LibreOffice, Microsoft 365) |
src/desktop.rs, src/desktop/ |
Windows and Linux desktop handoff, host election and Office launcher proxies |
src/naming.rs, src/naming/ |
Canonical identity, naming rules, aliases, --naming-worker mode and client |
src/naming/catalog.rs, src/naming/catalog/ |
Alias index keyed by naming::canonical_smiles; loaded once, fails closed |
licenses/name-aliases/ |
Alias data terms: OPSIN MIT and ChEBI CC BY 4.0, listed in NOTICE |
src/metafile.rs |
EMF import through the --emf-worker playback process on Windows |
src/cli.rs, src/cli/ |
Experimental reshiki --cli commands on an in-process HeadlessHost |
crates/mcp/src/ |
Experimental MCP stdio framing, protocol and ToolHost bridge |
src/app/bond_edits.rs |
Bond drawing settings and edits to selected bonds |
src/app/atom_edits.rs |
Element, charge, isotope, radical and mark edits to selected atoms |
src/app/ring_edits.rs |
Ring tool settings and the selected ring’s aromaticity |
src/app/selection_edits.rs |
Selection, grouping, framing and arrangement commands |
src/app/history.rs |
Undo and redo of drawing history |
src/app/view_settings.rs |
Window, camera and view-aid preferences |
src/app/engine_jobs.rs |
Chemistry engine requests and the handling of their results |
src/app/workspace.rs |
Command bar, context row, compact palette, inspector and status bar |
src/app/icons.rs, src/app/icons/ |
Original vector tool and command icons; one submodule per icon family |
src/app/nmr.rs, src/app/nmr/ |
NMR per-tab state, detached job, floating palette, docked report and plot |
src/canvas/nmr.rs |
Session-only NMR atom and shift labels on the canvas |
src/app/naming.rs, src/app/naming/ |
Chemical names inspector, the Import dock name field and name captions |
src/app/naming/aliases.rs |
Alias and name controls of Import → Name → Details and the inspector |
src/app/generic.rs |
Generic-structure edits and Expand generic structures into a new tab |
src/app/reaction_mapping.rs |
Reaction inspector: atom-map edits, Auto-map jobs and the review preview |
src/app/numeric_transforms.rs, numeric_transforms/ |
Exact numeric transforms, reference align/stretch drafts and field focus |
src/app/office.rs |
Office save targets, owned by each document tab |
src/app/desktop.rs |
The UI’s answer to desktop open requests on Windows and Linux |
crates/io/src/engine.rs |
Chemistry interface, native routing and response validation |
crates/chemistry/src/ |
Molecule preparation, bounded graph, properties and stereo calculations |
crates/chemistry/src/nmr.rs |
HOSE-style NMR environments (reshiki-hose-v1) and observed-shift lookup |
crates/io/src/nmr.rs, crates/io/src/nmr/dataset.rs |
NMR request, fingerprint, bundled index, prediction; nmrshiftdb2 transform |
crates/model/src/chemistry/ |
Drawing-to-molecule preparation and abbreviation detection and replacement |
crates/io/src/chemistry/ |
CDXML, MOL and reaction interchange, and selection cleanup |
crates/model/src/pictures/exchange/ |
Bounded raster decoding, orientation, transparency and reflection |
crates/model/src/editing.rs |
Clipboard remapping, transforms, component arrangement and ring placement |
crates/io/src/recovery.rs |
Atomic session snapshots and recovery candidates |
src/clipboard.rs, src/app/clipboard.rs |
Native multi-format Copy/Paste, asynchronous completion guards and safe Cut |
native/macos/src/clipboard.rs |
Bounded single-item AppKit pasteboard bridge |
native/windows/ |
Windows clipboard, printing and editable Office objects through a safe API |
crates/io/src/exchange/ |
Editable drawing export, bounded CDX/CDXML codec and legacy text encodings |
reference/engine/cdx_exchange.py |
Python reference codec retained for differential tests |
crates/io/src/export.rs |
Vector PDF and raster PNG from the shared SVG scene |
crates/model/src/storage.rs |
Write complete files beside the destination, then atomically replace |
crates/model/src/style.rs and assets/drawing_style.json |
Shared JACS / ACS defaults, publication units and font advances |
reference/engine/worker.py |
Independent Python/RDKit reference, enabled by rdkit-reference |
Document coordinates use screen-style positive-down Y, with 28 world units per RDKit coordinate unit. The default single bond is 42 world units, representing 14.4 publication points in the JACS / ACS preset. The camera never changes stored coordinates or export size. Native documents save as JSON format version 30, version 31 when they contain a custom arrow-label gap, version 32 for ORTEP settings, version 33 for linked-model state, explicit file-geometry provenance or atom labels, or version 34 for collapsed groups with two attachment atoms, and accept versions 1–34 when reading; see document format versions. Version 17 stores theme palette colors by name and custom colors as displayed; reading an older dark-canvas drawing converts its custom colors once to the lightness-flipped values it showed. Version 16 embeds custom themes and version 15 adds explicit reaction roles tied to arrow and atom IDs. A drawing from a newer ReShiki reports the document version it needs instead of a parse error. Version 14 adds validated per-document drawing settings; the physical coordinate scale remains fixed at 14.4/42 points per world unit. New presentation fields prompted version increments so older editors reject unsupported documents. History and camera are session state.
Atoms retain formal charge, isotope, explicit-H count, implicit-H policy, map number and tetrahedral winding. Winding refers to an explicit ordered list of stable neighbor IDs. Molecule preparation compensates for neighbor permutations, preventing array reordering from reversing a stereocenter. Double-bond stereo stores its reference atoms separately. Topology edits invalidate affected stereo and derived labels; a background refresh recomputes chemistry. Explicit Check also refreshes computed properties.
Document format versions
Section titled “Document format versions”VERSION in crates/model/src/document.rs is the newest format this build
reads. Its doc comment holds the table below; update both together.
Document::file_version selects version 34 when a collapsed group has two
attachment atoms, version 33 when a model retains linked-conformer source state,
a generation seed, explicit file-geometry provenance or atom labels, version 32 for thermal ellipsoids or nondefault ORTEP controls,
version 31 for a custom arrow-label gap, otherwise version 30. Newly generated linked models carry source state;
an unlabeled imported model identified by its cell or file atom sites does not
require version 33 by itself. A plain XYZ model without either needs version 33
to preserve its file-geometry marker.
Optional asymmetric-unit site provenance alone does not raise the version;
older readers can discard it safely.
ReShiki 0.13.0 reads versions up to 29 and cannot open
newly saved drawings; it reports the version it needs. Version 30 protects
distance-derived bond provenance: an older reader must not discard it and
resume chemical valence analysis after a crystal’s cell has been removed.
Legacy live-cell models supply provenance when opened; a legacy drawing
whose model was already discarded cannot recover that missing source context.
| Version | Added |
|---|---|
| 1 | Original native drawings. |
| 2 | Arrow styles. |
| 10 | Collapsed abbreviations. |
| 11 | Per-bond crossing depth. |
| 12 | Physical publication page layouts. |
| 13 | Embedded pictures. |
| 14 | Document drawing styles. |
| 15 | Reaction membership and roles. |
| 16 | Embedded custom themes (later also palette hues). |
| 17 | Exact custom colors on either canvas; newer-version probing. |
| 18 | Persistent atom and bond highlights. |
| 19 | Editable 3D depth appearance and captured projection stereo (#146; 0.11.0). |
| 20 | Retained EMF picture sources (#296); cubic arrow controls on a parallel branch. |
| 21 | Arrow target links and attachment IDs (cubic-arrow branch). |
| 22 | Persisted rear opacity (parallel branch). |
| 23 | Not used. |
| 24 | EMF sources, cubic arrows, target links and rear opacity integrated (#297). |
| 25 | Linked molecule-name captions and reaction atom-map labels (#298). |
| 26 | Generic structures and coordination joining (#300; 0.12.0). |
| 27 | Condition text attached to reaction arrows. |
| 28 | 3D models of structures (#386). |
| 29 | Crystal unit cells and atom sites on 3D models (#417; 0.13.0). |
| 30 | Persistent distance-derived bond provenance (#517). |
| 31 | Custom clearances for attached reaction labels (#600). |
| 32 | Thermal ellipsoids and molecule-only crystal display (#417). |
| 33 | Linked conformer snapshots, generation seeds and 3D atom labels (#386 phase 4). |
| 34 | Collapsed groups with two attachment atoms (#870). |
Chemistry requests
Section titled “Chemistry requests”ChemistryEngine accepts typed import, analysis, cleanup, abbreviation, aromatic-display and export requests. Native operations prepare immutable snapshots and return a complete response or an error. Parse, chemistry, layout and helper errors never start a Python fallback.
The optional rdkit-reference feature exposes engine::PythonEngine from crates/io/src/engine/reference.rs. Its original JSONL worker protocol remains available for differential tests: request IDs, protocol: 1, ok responses and stderr diagnostics. The reference process uses a prepared interpreter; it never installs packages. Custom backends remain available through LocalEngine::with_backend.
Iced tasks keep chemistry work off the UI thread. A document revision prevents late analysis/import/cleanup from replacing newer edits. Export uses the snapshot requested. Save records the exact snapshot written and a document epoch, so a late save cannot mark later edits as saved or redirect another document’s path.
Rendering and interaction
Section titled “Rendering and interaction”Both the canvas and SVG consume the same vector primitives. Canvas text is emitted as glyph outlines to maintain drawing order. SVG retains editable text and depends on compatible fonts in the viewer. Label placement and collision handling are still approximate.
A 3D model (crates/model/src/models3d.rs, #386) stores one molecule’s conformer, linked by atom ID, beside or in place of its 2D drawing. The scene paints it as Sphere, ShadedQuad, ShadedPolygon, Contour, Wire and Card primitives in exact colors, back to front, in four styles; Stick miters each atom’s half-bonds into one ShadedPolygon apiece, sorted by screen angle around the atom, with a single Contour stroke of them all behind the fills; a hydrogen growing in during the 2D → 3D morph is wrapped in Opacity. SVG writes native radial and linear gradients. Iced has no radial gradient, so the canvas (src/canvas/models3d/tiles.rs) rasterizes model primitives with resvg from the same SVG at twice the logical resolution, matching PNG export. It draws them as 512-pixel tiles on a grid fixed to the drawing, small enough for Iced to upload in the same frame. Each visible tile renders only the primitives that reach it and is cached by their content, so panning reuses tiles and editing one model keeps the others’; the cache holds at most 128 MB. While a molecule is shown in 3D, its 2D atoms leave the painted scene, rear-opacity paint, picking, selection regions, markers and erasing (Document::atom_drawn), and the model’s frame selects the molecule. The frame is a square around the model’s bounding sphere, so it does not change while the model turns. A chemical edit to an ordinary linked molecule retains its arranged model and marks its source chemistry as stale in transaction::reconcile. MOL, CDX/CDXML and SMILES export the 2D atoms and omit models. src/canvas/models3d.rs turns a selected model (models3d::turn, previewed and committed alike) and plays the 2D → 3D morph by setting the unsaved Model3d::morph on the canvas’s drawing copy, redrawing only the canvas. “From the 3D view” flattens a model through geometry::Prepared::flattened, the projection 3D optimize applies.
Linked models retain a chemistry fingerprint alongside their conformer. Staleness compares chemistry, including atom identities and stereo, independently of 2D positions and presentation. Structure changed · Update model appears for a selected stale model on the canvas and in the Structure section; the general floating Show-as chooser remains removed. Update model and Regenerate conformer use an abortable geometry job, reject results after document changes, preserve view rotation, frame, display and appearance, and replace the conformer in one Undo step only after success. The model’s Atom labels setting (None, Heteroatoms or All) follows visible atoms through the shared renderer and exports.
Generated geometry uses local indices for atoms, hydrogen parents and frozen bond endpoints. Each geometry atom has an optional live source ID; linked_atoms is the explicit list of surviving source links. Deletion clears source references and prunes saved Original drawing entries, so reused drawing IDs cannot acquire old coordinates. The fingerprint hashes dedicated element, charge, isotope, radical, hydrogen/valence, connectivity/order and perceived stereo records; nondirectional endpoints are normalized. UI stale comparisons are cached per tab, file epoch and document revision. Replacement conformers are rigidly aligned to surviving source correspondences with a 3×3 least-squares fit, applied to all coordinates before restoring the saved view rotation.
Stale models disable From the 3D view with Update the model first; direct flatten actions enforce the same guard. Original drawing remains available.
Splits and joins retain each model’s surviving source links and frozen rendering chemistry. New atoms in the connected source are included in the generation target without assigning them to a model automatically. Update replaces all models linked to the updated molecule with one model, preserving the selected view’s settings and the union of the saved Original drawing snapshots in the current drawing frame. Source links remain disjoint. A source spanning disconnected fragments cannot generate a conformer and leaves the model unchanged. There is no automatic fragment partitioning or model consolidation outside explicit replacement.
A model read from a crystal or XYZ file (crates/io/src/crystal.rs, #417) may hold several whole molecules and stores the unit cell in Model3d::cell: the lattice vectors in ångström, with the model’s Cartesian positions relative to the cell origin, and the bonds that leave the cell as atom, partner and cell offset. Model3d::sites keeps each atom’s file label and displacement parameters (U in Ų). A model with a cell, file atom sites or an explicit file_geometry marker keeps imported geometry and is excluded from linked-model staleness, Update and Regenerate conformer. The cell’s hidden edges and the half-bonds leaving the cell are dashed Wire primitives; the frame’s bounding sphere holds the cell corners and labels. See Crystal structures.
Legacy models without a fingerprint or file-geometry provenance use the current chemistry as their initial generated baseline, unless a cell or file atom sites identify imported geometry. Update and Regenerate remain available under that policy. A legacy plain XYZ model with no cell, sites or import marker is indistinguishable from a generated conformer; replacing its coordinates requires an explicit, undoable action. Reopening the original XYZ file records its provenance.
Pointer motion is read from each event, rather than only Iced’s latest cursor snapshot. This matters when multiple move/press/release events arrive in a single batch. The desktop drag test found this issue; a regression test now reproduces that event sequence.
An Iced sensor reports actual canvas dimensions for Fit, including window resizing and inspector visibility or width changes. Fit follows size changes until the user manually pans or zooms. This updates only the camera. Tool shortcuts ignore key events already captured by text inputs. Workspace state (inspector tab and grid visibility) is currently per-session. src/app/import.rs routes chosen or dropped files: structures and pictures insert side by side as one Undo step, and .rsk drawings open through the save prompt. Window drag events carry no pointer position, so insertions land at the pointer only once the system reports it over the canvas again, otherwise at the view center, and move inward so they stay in view.
The file-open panel is intentionally unfiltered, so opening a supported file does not depend on macOS type registration. Parsing and document validation enforce supported content after selection. Desktop tests observed a delayed Open-button enablement both with and without filters, so the cause is not established; subsequent native reopen checks succeeded.
Depth appearance and rear opacity
Section titled “Depth appearance and rear opacity”Depth appearance fades atom, bond and fill colors by depth; it is in ReShiki 0.11.0 and later. Rear opacity makes view-occluded ink transparent; it is in ReShiki 0.12.0 and later. Both are stored as depth_appearance::Scope entries in Document::depth_appearance. Positive Z faces the viewer. See rear opacity for what users see.
- Scopes. A scope’s
atomsare its RGB owner. Owners span connected components, including coordinate contacts, andstrength,weightsandoverridesdrive the RGB fade. Rear alpha targets whole covalent molecules instead:rear_opacityis the scope’s alpha, andrear_overrideskeeps per-atom alpha inside a wider RGB owner. A scope with strength zero carries alpha only.rear_weightsis still written for file compatibility; visibility never reads it. - Paint order.
scene::primitivesbuilds onerear_opacity::Paintfrom the raw document and its retained XYZ, before any color change.BondInputs::prepare(scene/bond_pass.rs) then materializes the RGB fade and the canonical canvas colors on a copy, so no stage fades the same paint twice. Ring knockouts build one morePaintof that copy, on the first ring stroke. With visibility active,paint_rear_before_frontmoves faded bond ink ahead of opaque bond ink and keeps the order inside each group. - View visibility.
rear_opacity/visibility/recomputes geometry from the retained XYZ; RGB or frozen paint never acts as a camera. It has two kinds of occluder. Inferred cages (cage.rs) are covalent components of 4–256 visible atoms in which every atom has three bonds and the faces close a polyhedron. Occluder ink (occluder_ink.rs) is the real projected ink of other objects, fromscene::occlusion_ink. Ink in a rear-opacity scope becomes transparent only where an occluder in front hides it. - Rim exemption. A cage rim (
rims.rs) is exposed to its own shell, so the whole rim stays opaque at every setting, including its stroke width, which has no XYZ of its own. An exposed rim atom keeps its original shared bond corner. - Budgets and the opaque fallback.
Visibility::newchecks conservative limits fromvisibility/budget.rsat several points. It estimates the line queries from the managed ink before it searches for cages, stops flattening occluder ink after 100,000 edges, and checks the total work, such as 1,000,000 line queries, once cages, rims and ink are built. The estimates are weights, not an exact operation count. Over any limit it returns no visibility, and the drawing paints opaque exactly as without rear opacity. A flat drawing, with every atom at one depth, also skips visibility and keeps its paint order. - Projected double-bond rails.
scene/projected_bonds.rsplaces a double bond’s second rail in its retained ring face, projected without rescaling XY.BondPassbuilds oneFaceIndexper scene; it memoizes faces by stored endpoint order and gives each face search its own budget. Each rail gets opacity intervals along its own centerline. - Attachment hit tests.
scene/attachment_ink.rslets a new mechanism-arrow target snap only to ink that is still visible, with the same opacity, crossing and rail clipping as the bond scene. Saved arrow links do not pass this test.
Native chemistry
Section titled “Native chemistry”Application code forbids unsafe. Platform calls stay in native/windows, native/macos and native/process (child-process limits for the naming worker), and the bounded worker allocator stays in crates/process-heap. Sanitization and ring failures use thiserror, preserving the failed stage and underlying ring error. Tests, examples, build scripts and the Windows helper use anyhow for propagation and context. Existing string-error APIs are converted explicitly at those boundaries. Prefer iterator transformations when they clarify data flow; use bounded loops for stateful graph traversal.
The app uses LocalEngine, which implements ChemistryEngine. Rust handles imports, analysis, cleanup, abbreviations, aromatic display and molecular/drawing exports. It prepares chemistry, computes properties and full CIP labels, lays out molecules and reconstructs drawings; an isolated worker process of the same executable supplies InChI. Blocking tasks keep chemistry off the UI executor. Cached drawing labels never determine chemical identity.
Default builds have no Python worker or uv setup. Ordinary tests exercise the native application. cargo test --features rdkit-reference adds the original backend, captured native fixtures and live differential tests. This feature is disabled in release packages and is not a runtime plugin ABI.
reference/cdx_codec.rs compares binary output and decoded XML with the Python reference, including native fixtures and every supported property. Its text corpus covers every defined character in the supported legacy codepages. reference/engine_migration.rs compares complete responses, stereo identities, isotopes, charges, radicals, figure objects, and rejected queries against RDKit. Malformed binary data must fail before reaching the chemistry backend. The ordinary integration tests use LocalEngine, so they exercise the app’s migrated path.
reference/properties.rs checks all 119 element entries, 3,111 known isotopes, unknown isotope fallbacks, templates, ions, radicals and explicit/implicit hydrogens against RDKit. Formulas and counts must match exactly; masses allow relative error of at most 1e-12 for platform-dependent floating-point operations. JSON parsing preserves full float precision. Reference-bridge tests retain checks that prepared descriptors are not recalculated in Python.
reference/valence.rs compares over 278,000 cases with RDKit’s strict/intermediate property caches and radical pass: allowed/rejected valences, charges, implicit-H policy, aromatic and partial bonds, dative direction and metal atoms. Graphs reject invalid endpoints, duplicate bonds and excessive size. Intermediate caches tolerate temporary valence excess during normalization; final validation remains strict.
The Rust sanitization pipeline combines functional-group and metal normalization, radical assignment, canonical atom ranking, Kekulé bond assignment, aromaticity, conjugation, hybridization, stereo cleanup and hydrogen restoration. reference/sanitize.rs compares 32,987 complete results with RDKit, including valence caches, bond directions, stereo groups, canonical retries and rejected inputs. Intermediate caches follow the reference stage order. A failure returns its stage without changing the input. All native chemistry operations use this preparation pipeline.
reference/document_preparation.rs compares complete drawing-to-molecule states against direct RDKit APIs, including stable IDs above the JavaScript integer limit, reordered bonds, mirrored coordinates, stale labels and rejected chemistry. reference/test_prepared.py verifies native-state transport and proves that migrated requests skip Python sanitization and stereo perception. The optional reference adapter checks its RDKit valence/ring caches against Rust. Preparation errors stop the request. Document validation indexes endpoints and neighbors, with a 20,000-atom regression.
Kekulé assignment uses bounded, iterative backtracking and preserves bond directions according to the reference rules. reference/kekulize.rs compares over 51,000 cases, including rejected graphs, dummy atoms and wedged bonds. It also checks the optional-attempt snapshot used by canonical retries: aromatic flags/orders are restored after a chemical failure, while some direction and hydrogen changes remain. Failed assignment leaves the caller’s graph unchanged.
Canonical ranking uses a separate record for atom maps and stereo metadata. reference/ranking.rs compares over 45,000 rank vectors against RDKit, including symmetry classes, different ring caches, tetrahedral and bond stereo, stereo groups, and atom permutations. It also checks over 10,000 bond assignments using Rust-generated ranks. Ranking reads existing stereo annotations; perception is a separate pass.
crates/chemistry/src/smiles/write.rs prepares components, ranks atoms, assigns Kekulé bonds when requested, adjusts stereo and assembles plain SMILES. Prepared molecular imports, analysis and SMILES exports use it asynchronously; transport tests forbid the native SMILES writer on these paths. Independent tests compare full strings and atom/bond order, including mixtures, maps, ring stereo, coordination winding and custom symbols. Malformed states, 20,000 disconnected atoms and combined text limits have separate checks.
Small C++ reference fixtures verify the shared ring/traversal sorting and its heap fallback on each platform. Regenerate them with reference/smiles_sort_reference.cpp and the platform compiler; production uses only the checked Rust implementation. Adapted standard-library algorithms and their licenses are recorded in licenses/stdlib/, which release packages include.
Stereo assignment uses a separate atom-priority pass. reference/cip_ranking.rs compares 43,190 cases with RDKit’s direct legacy ranking API, including isotope priorities, explicit zero and maximum maps, hydrogens, special bonds and reordered atoms. Rust bounds refinement work and storage, handles empty graphs, and uses dynamic neighbor lists. These priorities support the reference’s legacy stereo perception; they do not replace full CIP labeling. reference/perception.rs compares 35,783 complete assignment results, including legacy R/S and E/Z labels, ring-stereo relationships, cached properties, unknown stereo and group repair. Errors leave inputs unchanged; group expansion and iterative refinement are bounded.
Metal cleanup converts eligible single bonds to donor→metal bonds while preserving atom, stereo and display metadata. reference/organometallic.rs checks 25,209 cases, including multi-metal complexes, existing dative bonds and rejected inputs. It also compares the exact cycle order of the bounded, iterative fast ring pass used by ranking before full ring perception.
reference/electronic.rs compares pi-electron counts, conjugation and hybridization in 59,631 cases. Coverage includes all elements, radicals, charges, special bonds, coordination geometry, explicit hydrogens and the NCI molecule sample. These passes return separate annotations without changing the graph; stereo perception follows them.
reference/stereo.rs compares atom-chirality and atropisomer cleanup in 47,835 cases, including coordination permutations, ring-size limits and stereo-group membership and IDs. Cleanup repairs existing annotations; it does not assign absolute configurations. Invalid metadata and excessive work or group expansion return errors without changing the input.
reference/drawn_stereo.rs compares 62,137 cases for atom winding from wedge/hash geometry and cis/trans annotations from up/down bond directions. Coverage includes conflicting wedges, near-linear or overlapping bonds, mirrored/scaled drawings, existing tags and explicit-H promotion. Coordinate validation and batched atom-cache updates keep errors atomic and large drawings bounded.
reference/bond_geometry.rs compares 39,661 cases for double-bond direction assignment from coordinates or existing stereo tags. Coverage includes conjugated chains, ring-size limits, unknown stereo, near-linear geometry, atom/bond permutations and absent conformers. Propagation uses a bounded stack; invalid coordinates or exhausted limits return errors without changing the input. Legacy R/S and E/Z assignment uses the separate perception pass.
Rust can select and orient wedge/hash bonds from existing stereo tags, including optional second wedges and 2D/3D atropisomers. reference/wedging.rs compares complete states with RDKit’s molecule and single-bond APIs. Rust preserves atom order when selection priorities tie; tests verify those choices against native outputs under equal-priority atom permutations. Ring membership must remain identical, and Rust must preserve the original cache order. Invalid inputs or exhausted work limits leave the caller’s state unchanged. The application now uses it when reconstructing analyzed drawings and molecular exports.
reference/drawing_output.rs compares 12,218 drawing outputs with direct native bond-assignment APIs and the original document converter. Canonical ranking matches the reference’s drawing defaults. Full CIP results include changed bond-stereo controls as well as labels; reconstruction maps both back to stable IDs. Styles, groups, captions and aromatic circles are retained, and the result remains one undoable edit. Imports, cleanup and figure interchange use the same checked Rust drawing conversion.
Aromatic-circle toggles prepare both chemical states, update selected rings and verify matching canonical SMILES in Rust before returning the edit. engine::native_aromatic completes analysis through the isolated InChI worker without starting Python. Independent tests compare complete responses and rejected inputs, including partial fused-ring selections, heterocycle hydrogens, stereo and undo/redo. Guarded routing checks cancellation, concurrent snapshots and helper failures without fallback.
Abbreviation detection and replacement run in Rust from one catalog file, crates/model/src/chemistry/abbreviations/catalog.json. Each entry’s SMILES is parsed at load time to derive its matching query and formula, and laid out with the native depiction port for its template coordinates. Matching preserves native priority, overlap and selection rules; replacement retains attachment IDs, styles and group/reaction membership. A frozen per-platform RDKit capture of the original 62 groups guards their layouts. Independent tests compare original-worker results, complete app responses, concurrent requests, numeric transport and undo. Final drawing reconstruction and full CIP labeling use Rust; the isolated InChI worker supplies InChI. The adapted coordinate norm and its license are recorded in licenses/cpython/.
MOL export writes V2000/V3000 in Rust from the prepared molecule. Dative bonds, large graphs and large coordinates select V3000 automatically. reference/molfile.rs compares exact native output, including wedge endpoint reversals, unspecified double bonds, isotope/charge/radical records and format boundaries. Engine tests reimport exported structures and compare identities, atom maps, masses and stereo. They also preserve the existing rejection of generic R atoms as queries on MOL import.
MOL import runs in Rust on a blocking task. The reader in crates/io/src/chemistry/molfile/read/ parses V2000/V3000, applies file-specific valence/hydrogen rules, then runs 2D/3D stereo and sanitization. Drawing reconstruction retains file coordinates, generated wedge directions and dummy labels. File annotations retain the conformer dimension and signed attachment values, including explicitly stored zero. reference/molfile_import.rs compares complete molecular states with direct RDKit imports, including legacy property records, enhanced stereo collections, substance groups, atom maps, continuation lines, templates and malformed files. The optional RESHIKI_RDKIT_SOURCE path adds the reference source’s MOL and substance-group fixtures. Substance-group SMARTS uses Rust syntax validation, including CX extensions: valid queries exceed the editable contract, while invalid query text is ignored as in the native reader.
reference/molfile_drawing.rs compares complete drawing states and editable output, including full CIP results. reference/engine_migration.rs compares complete import responses. Default MOL imports complete without Python. The retained bridge tests forbid native parsing, sanitization, stereo assignment, Kekulé/wedge generation and drawing conversion for prepared imports. Its signed ring-stereo transport uses a bounded, version-checked native binary property block because Python has no atomic integer-vector setter.
Substance-group parsing checks members, crossing bonds, attachments, defaults and continued data fields. Chemistry-changing records run in native order, including charge/H overrides, aromatic H counts and conversion of unspecified/query bonds to coordinate bonds. Coordinate-bond replacement adjusts explicit H on both endpoints and clears bond stereo. Tests cover both accepted and rejected records, native numeric-field behavior, nonsequential bookmarks and bounded default expansion.
reference/smarts.rs compares query acceptance and atom counts with direct RDKit parsing. Coverage includes recursive and Boolean queries, range expressions, isotope/H/charge syntax, chiral permutations, ring closures, names and UTF-8 boundaries. CX extensions validate coordinates, escaped labels, atom properties, bond references, stereo groups, link nodes, variable attachments and substance groups. Branches and negation chains are iterative; recursive queries, input size and atom counts have explicit limits. The same query corpus also runs through native MOL substance groups, without skipped parser cases. This validator does not replace query matching or the SMILES importer.
SMILES import runs in Rust through crates/chemistry/src/smiles/: names, CX annotations, hydrogen removal, sanitization and stereo perception. Shared CX parsing retains annotation order, coordinates, labels, radicals, bond changes, stereo groups and protected substance-group members. Independent tests compare prepared states and conformers; a separate suite checks coordinate values bit for bit, including each platform’s hexadecimal and underflow rules. Optional numeric annotations fail only when an operation reads them, matching native import behavior.
RXN and reaction SMILES export run in Rust through crates/io/src/chemistry/reaction.rs. It preserves explicit participant roles, coefficients, maps, stereo and aromatic bond types without changing the drawing. Differential tests compare complete files and rejected inputs; a backend-free test verifies the runtime path. Reaction SMILES sorts participants within each role, groups disconnected components and expands coefficients. Backend-free tests cover both reaction export formats.
RXN and reaction SMILES import use Rust readers and canvas assembly, preserving participant roles, stereo, label spacing, reagent rows and separators. Placement keeps full precision until the final canvas coordinates. Rust labels each participant, prepares the combined analysis graph after labeling and writes its SMILES. RXN and reaction SMILES use the native response path; the Rust solver supplies missing participant layouts. Complete-response and concurrent-import tests compare the original importer. Incomplete labels or invalid coordinates cannot publish a partial drawing.
Reaction SMILES import retains explicit H, grouped reactants, disconnected agents and global CX annotations. Existing conformers keep their coordinates and dimension; the Rust solver lays out only participants without coordinates. CX attachment markers guide Rust wedge selection. Untyped CX properties travel separately to the layout pass, preserving numeric conversion errors only when the native algorithm reads those values.
Rust supplies 2D coordinates, labels and the editable drawing; the isolated InChI worker supplies InChI. CX coordinates used for perception can be nonfinite; they never enter drawing APIs. Complete-response tests include templates, isotope/stereo cases, names, malformed input and optional source fixtures. Concurrent imports keep separate layout results. Transport tests forbid native parsing, H removal, sanitization, Kekulé/wedge generation and drawing conversion. Explicit zero atom maps and independent atom/bond aromatic flags survive transport.
Hydrogen removal preserves isotope H, protected group members, winding and double-bond controls. Invalid properties on removed hydrogens are discarded before validation. Query bonds removed with hydrogen are allowed; surviving queries are rejected. Malformed metadata and excessive work return typed errors without changing input.
Axial stereo detection runs before sanitization in the MOL reader and on the sanitized graph during drawing preparation. reference/atropisomer.rs compares exact tags with native unsanitized MOL and CXSMILES reads, including 2D, 3D and absent coordinates, aromatic rings, reversed bonds and ambiguous geometry. Candidate collection and neighbor access stay linear for high-degree graphs. Errors leave the caller unchanged. Editable drawings retain the author’s axis-adjacent wedges and recompute axial tags from their Y-up 2D coordinates; no new native stereo field is stored. Full CIP supplies M/P bond indicators. MOL/CDXML round trips and CXSMILES imports with coordinates and wedges retain this configuration where the 2D drawing can show it; plain SMILES and InChI have no axial stereo layer. A MOL file opens without an axis the drawing cannot keep (3D files, and axes next to a crossed or ring double bond: #1057, #1064). An RXN participant whose drawing would change its axes, and a SMILES or InChI import whose fresh drawing would lose an axis, are refused. Naming, generic-product identity and 3D geometry still reject axial stereo where they cannot preserve it.
reference/spatial_stereo.rs checks 3D atom perception against the native geometry API, including tetrahedral winding, all 53 coordination permutations, geometric tolerances, existing tags and annotation presence. The pass preserves connectivity and hydrogen counts, clears the stereo cache only for 3D conformers, and returns typed errors without editing inputs. The MOL reader uses it before sanitization; unsupported coordination classes retain the existing import rejection.
Editable drawing export runs in crates/io/src/exchange/drawing/, including rich text, labels, marks, arrows, graphics, pictures, bond crossings, nested groups and abbreviations. reference/drawing_exchange.rs compares XML object ownership, attributes and text against the original Python writer; PNG comparisons check decoded pixels. Engine tests also compare binary exports byte for byte and reimport chemical identities. The writer preserves unsupported-feature rejections and bounds XML size, object count, nesting and graph work.
The Rust CDXML libraries expand abbreviations, normalize bond depictions and parse molecular fragments without inventing sanitization caches. Separate readers preserve drawing styles, rich text, bond appearance, marks, labels, arrows, graphics and groups. They retain native f64 values and source-object identities until checked document conversion. Chemical preparation combines fragments, preserves coordinate scales and runs sanitization and stereo perception in native stage order. Scene assembly preserves object ownership, aromatic circles and final appearance. Differential tests compare original scenes and the actual JSON-to-document transport. Default CDXML/CDX imports complete natively, generating molecular coordinates when absent.
Rust computes symmetric SSSR rings with iterative, bounded searches. reference/ring_perception.rs compares ordered atoms and bonds against native RDKit, including NCI molecules, permutations, dense graphs and disconnected components. Greedy pruning uses the pinned platform’s equal-key sorting policy from crates/chemistry/src/native_order/, shared with SMILES traversal. Ring analysis and preparation no longer fall back to Python or accept transported ring overrides.
Runtime analysis derives InChIKeys in crates/chemistry/src/inchi/key.rs using safe Rust and software SHA-256. Rust derives the key from the generated InChI; export reuses that string. Empty or unsupported identifiers retain empty keys. Independent tests compare native keys and error codes, layers and platform integer behavior; an optional pinned 1,181-entry corpus adds chemical coverage. Malformed non-ASCII starts use deterministic ASCII validation; tests report native locale differences separately.
chemistry::inchi::input prepares owned atom, bond and stereo records for InChI 1.07.3 without FFI. Independent captures compare the exact arrays passed by the original adapter to its generator, including conformer absence, isotope/H handling and ordered bonds. Invalid indices and undefined native stereo inputs return typed errors without changing the source molecule.
chemistry::inchi::generator relaunches the application with --inchi-worker, using the pinned cosmolkit-inchi Cargo dependency. ReShiki supplies its existing molecular property cache, sanitization and stereo routines through toolkit traits. Bounded versioned frames carry molecular graphs and diagnostics; the dependency does not expose raw C records or warning masks. Deadlines and cancellation terminate the helper process. A Rust allocator bounds live allocations during the operation, including the adapter and response; this is not an RSS or stack limit. Runtime routes use current_exe(); an absolute developer override can select a transport-test executable. Release packages contain exactly one application executable. Build metadata records the locked InChI dependency, and checks validate architecture and worker operations after extraction or installation. See tools/inchi-helper/README.md.
Runtime InChI reconstruction runs inside the Rust helper through the dependency’s molecular API and ReShiki toolkit callbacks. The async reader returns the reconstructed state, diagnostics and unspecified-bond identities; it compares 1,848 original imports and complete reconstruction outcomes, plus 25 text boundaries. chemistry::inchi::output supplies the sanitization and hydrogen-removal callbacks and retains reconstruction from detached C records for independent development reference checks, including all 19 reachable cleanup rules. Those C records do not cross the runtime helper protocol. Native InChI imports then use the Rust layout and drawing pipeline.
Cargo builds the app and its worker modes from one locked dependency graph. The InChI compatibility fixes are part of upstream COSMolKit 0.5.0 at the pinned Git revision; the build does not patch a private C source copy. A separate development-only executable shares the worker implementation for independent transport and fault tests; it is never copied into releases.
engine::native_response builds the default Analyze, abbreviation and ordinary molecular export responses from immutable prepared state. Blocking chemistry runs off the async executor; helper cancellation cannot publish an edit. engine::native_import completes imports with existing coordinates or generated Rust layouts. Differential tests compare complete original-worker responses and rejected inputs. Isolated subprocess tests forbid Python startup on native routes and check helper failures without fallback. Reference-feature tests retain the independent original backend.
chemistry::depict performs deterministic 2D layout: ring construction, neighbor attachment, stereo seeds, templates, fragment expansion, collision correction and final placement. Complete layouts match the original public API on Linux x64, macOS ARM64 and Windows x64, with exact coordinate bits and unchanged chemical state. Each stage also has independent native fixtures. reference/depict_numeric.md records platform arithmetic, and reference/depict_pipeline.md records the complete-layout contract. Those local comparisons do not establish ARM Windows/Linux graphical acceptance.
Coordination seeds use the current request’s bond length. Native static caches retain the first requested length; the Rust solver instead matches a fresh native call, so an earlier drawing cannot change a later drawing’s style. Forward/reverse requests and failed custom-length requests check this policy.
chemistry::cleanup prepares selected components and merges checked layout results while preserving fixed atoms, remote bonds, styles and chemical/visible stereo. Independent tests compare 308 complete responses and 22 rejections on all three local platforms. The default engine completes cleanup through this pipeline. Native assertion-only warnings use truthful Rust diagnostics; helper failures cannot become partial success.
Full CIP migration includes read-only molecular contexts and lazy graph expansion in stereo::cip. Persistent visit maps share unchanged branches, avoiding a molecule-sized history copy per node. Expansion, re-rooting and searches are iterative and bounded; failed expansion cannot supply a partial labeling graph.
Sequence comparison and priority sorting use an explicit work stack with bounded storage and shared iteration limits. All nine full CIP sequence rules, tetrahedral/double-bond/axial configurations and auxiliary descriptor assignment are implemented. The complete labeling pass returns detached state and neighbor orders, bounds retained graph storage, and leaves input unchanged on failure. Native-reference tests cover complete labels, selection, stale codes, dependent centers and rejected inputs. Drawing tests compare Rust labels and finished documents with the original converter. All native imports, analysis, exports and cleanup use Rust labeling. Reaction imports label each participant before canvas assembly. The optional reference bridge retains local_cip validation tests, including concurrent requests and undo; invalid label responses fail before publishing a drawing.
reference/cip_molecule.rs and reference/cip_digraph.rs compare recorded native API results, including partial Kekulé failures, cache-access order, lazy expansion, re-rooting, duplicate fractions, isotope mass bits and edge ordering. Input hashes guard against stale reference data. Duplicate visit distances retain the native C char byte representation and use the target platform’s signedness.
Regenerate CIP fixtures on macOS with uv run --locked python reference/build_cip_molecule_oracle.py --rdkit-source ~/dev/rdkit, adding --component digraph, --component rules, --component pairing or --component configuration for graph and comparison fixtures. This optional helper links the pinned wheel’s native library and requires Apple clang and Boost headers. Fixture replay uses the reference Python environment but needs no C++ compiler or source checkout. reference/cip_labeling.rs also calls the public labeling API directly. Set RESHIKI_CIP_VALIDATION_SUITE to the pinned source’s Code/GraphMol/test_data/compounds.smi to include its 300-compound suite; the test checks its checksum.
Rust computes logP, polar surface area and hydrogen donor/acceptor counts using pinned descriptor rules and a bounded query matcher. reference/descriptors.rs compares totals, atom contributions and Crippen type assignments against RDKit, including explicit hydrogens, atom permutations, special bonds and the reference’s 1,000-match limit. Floating-point results allow relative error of at most 1e-12; counts and types match exactly. The suite also checks molar refractivity and sulfur/phosphorus surface contributions, although the UI does not expose them.
Embedded PNG, TIFF, JPEG, GIF and BMP normalization runs in Rust. Imports validate and decode picture payloads before exposing a document. Export supplies prepared PNG data, including lossless row reversal for reflected pictures. Image work runs off the UI thread with the existing size and document budgets. Lossless pixels must match the Pillow reference exactly; JPEG color channels may differ by at most 2/255 between decoders. Alpha must match exactly.
The documented vendor/zune-jpeg patch preserves libjpeg’s small-component chroma sampling and integer transform precision. Independent JPEG tests compare image edges, sampling ratios, orientation, opacity and scalar/SIMD output. Release packages retain its original licenses, IJG terms and modification notice.
Pillow is a development-only image reference. The minimal RDKit reference-environment check also runs without Pillow; neither is included in release packages. TIFF coverage includes palettes, grayscale alpha, integer/float samples, compression, EXIF orientation and associated alpha. Planar 16-bit fixtures also check known source samples against Pillow’s libtiff reader, avoiding a channel-decoding bug in its default raw reader.
Regenerate codec constants and codepage tables with uv run --locked python scripts/regenerate_cdx_rust_schema.py, followed by cargo fmt --all. The generator uses the checked Python schema and standard-library codecs; released applications read only compiled Rust constants and tables.
Element, isotope, allowed-valence and outer-electron data come from RDKit Release_2026_03_6. Regenerate them with uv run --locked python scripts/regenerate_atomic_data.py --rdkit-source ~/dev/rdkit, then cargo fmt --all. The generator verifies the source checksum and compares every entry with installed RDKit. Its BSD license and attribution are in licenses/rdkit/ and are included in release packages.
Regenerate descriptor rules with uv run --locked python scripts/regenerate_descriptor_data.py --rdkit-source ~/dev/rdkit, then bun fmt crates/chemistry/src/descriptor_data.json. The generator verifies source checksums and compiles the fixed queries into checked-in data. The application reads that data without invoking Python or parsing SMARTS.
Packages contain no Python interpreter, worker project, uv environment or companion executables. Archive and installer checks exercise the relocated application with Python/uv unavailable and reject chemistry-cache creation. macOS bundles retain their icon, license notices and signing. Windows upgrades retire the app-owned old chemistry directory, InChI helper and expanded license trees, preserving user data and caches. The Licenses directory contains the four project license/notice files and THIRD-PARTY-NOTICES.txt; indexed SHA-256 references share only byte-identical texts, preserving every dependency declaration, nested notice and source attribution.
Native clipboard
Section titled “Native clipboard”On macOS, explicit Copy/Paste relaunches the same executable with --clipboard-worker, using Rust AppKit bindings with JSON on stdin/stdout and base64 representations. The helper prepares one item with a private ReShiki document, supported editable binary drawing data and PDF/PNG/SVG alternatives. Copy Image omits the editable structure and adds an embedded raster drawing object with physical bounds for readers that ignore PNG resolution metadata. The helper does not monitor clipboard changes or read previous contents during Copy.
The app accepts format: "cdx" with base64 input/output. LocalEngine converts it in Rust and applies the same native import path as CDXML. The binary codec bounds input, output, nesting, object count and property count. Unsupported object properties and query predicates return errors. The optional reference worker retains its original CDX path as a test oracle.
Clipboard tasks capture document epoch and revision. Cut removes the captured selection only after a successful write and only if the drawing remains unchanged. Paste validates and inserts in one Undo step, rejecting stale results. Rendering uses a snapshot and runs off the UI thread. The same entry point works in development and installed packages; no Swift compiler or separate clipboard binary is needed. Printing uses --print-worker with a bounded PDF snapshot and Rust AppKit/PDFKit bindings, keeping native dialogs off the editor event loop. Windows uses its native clipboard and editable Office object bridge. Linux exchanges drawings, images and explicit text formats through X11 or the focused Wayland window’s clipboard connection. Ordinary Copy supplies no chemical plain text on any platform.
Chemical abbreviations store presentation metadata over the complete atom/bond graph. Cleanup requires selected atoms in the desktop. Rust splits connected components, redraws the requested atoms/molecules, pins unselected atoms and preserves each component’s placement. Cleanup results remain transient until Apply; a revision and document epoch reject stale previews, and Apply commits one history step. Template connection preview and insertion use the same pure geometry operation.
Aromatic circles are derived from closed cycles of aromatic bonds (order 4), without a detached graphic in the native model. Rust changes selected rings and checks their chemical identity before publishing the edit. Editable exchange emits aromatic bonds and owned circle graphics; import recognizes these circles while retaining independent ovals. Contextual atom/bond shortcuts run only after focused widgets have ignored a key.
Reviewed assistant proposals
Section titled “Reviewed assistant proposals”The optional Codex panel uses a local app-server child with structured output. The child has an isolated temporary working directory, shell and external integrations disabled, bounded JSONL transport, cancellation, and a turn timeout. Finder launches receive known executable search directories without evaluating shell startup files. ReShiki imports proposed SMILES through the native chemistry engine and builds typed drawing objects with current styles. Chat and previews are transient; document epoch/revision checks protect Apply, which commits one ordinary history entry. The assistant never owns the live document.
Generation emits public activity, composition plans, actual structure counts and retained draft previews independently of the final structured proposal. Measured layouts support rows, central examples, grids and shared-reactant branches. The same app-server session then reviews overview and close-up images with editable object data. Corrections are validated and rendered again; only an exact final draft with no unresolved findings can pass the automatic-acceptance gate. Cancellation retains completed previews, and closing the panel leaves the job running. Preview geometry is cached independently of the activity timer. See assistant flows and limits.
Bond Z order is presentation metadata. A bounded sweep detects unconnected crossings, then clips lower-bond line/polygon geometry. Canvas and exports share these primitives. Elbow arrows use two line segments and retain a movable corner through native and supported editable interchange.
Generic structures
Section titled “Generic structures”Document::generic holds element and group alternatives, link nodes and repeating units; variable attachment points are atom data. They stay drawing data until Expand generic structures materializes them, and expansion never changes the source. Generic structures lists the supported definitions and limits. The module root is crates/model/src/generic.rs; its submodules live in crates/model/src/generic/.
| Module | Job |
|---|---|
generic.rs |
Typed definitions, Limits, notices and the public API |
definitions.rs |
validate, prune and selection |
authoring.rs |
Label parsing (alternative, apply_label) and repeating-unit definitions |
guards.rs, references.rs |
Whether an edit touches generic data, and inherited references of changed components |
expansion.rs, materialize.rs |
Bounded finite enumeration and concrete fragment insertion |
variable.rs, depiction.rs |
Placement of variable substituents and generated link and repeat parts |
clearance.rs, ink.rs, certify.rs |
Clearance certificates, ink measurement and checks that preserved data and translations are exact |
symmetry.rs |
Exact chemical equivalence of completed products, for duplicates |
budget.rs, prepared.rs |
The work allowance and its cost model; the prepaid input and the sealed result |
limits.rs, errors.rs, graph.rs, geometry.rs |
Shared bounds (also used by reshiki-io), refusal text, ordered adjacency and bounds helpers |
probe.rs |
Stage counters for tests; a no-op in production builds |
The budget rule is: charge before you clone or scan; one allowance per request; never reset. A request gets one Work allowance (Limits::default().max_work, 50,000,000 units). Every stage debits that counter before it copies a document, scans ink or measures labels, and no stage starts a fresh budget. Running out rejects the whole request; nothing is installed.
Insertion has two phases, so the UI thread never runs the expansion (src/app/generic.rs):
generic::prepare_expansion_inputborrows the live drawing and selection. It checks the limits and pays the presentation and selection costs before it copies the snapshot into an ownedExpansionInput.generic::expand_preparedruns on a blocking task with the remaining allowance. It enumerates, lays out, measures and validates the products and returns aPreparedExpansion.transaction::take_expansionmoves the document out of the result before the app opens a tab.transaction::install_expansioninstalls it into that new, empty tab. It does not run reconcile again, because the result is already validated.
PreparedExpansion holds its document in a single-use Arc<Mutex<Option<Document>>>. The result travels inside an Iced message, and messages are cloned. A clone copies only the Arc, and take empties the Option once, so a cloned or replayed message cannot install the drawing twice, and a failed take opens no tab. The app accepts a result only for its pending tab and file epoch. It reports a source tab that was closed or replaced, and discards a result that arrives while a blocking modal is open.
The io boundary:
- CDXML and CDX import:
crates/io/src/chemistry/cdxml/generic.rsdetaches typed generic data before the molecular parser runs and restores references through source object IDs. - CDXML and CDX export:
crates/io/src/exchange/drawing/generic.rswrites ChemDraw generic properties on their atom and bracket IDs. - MOL: the reader in
crates/io/src/chemistry/molfile/read/fillsGenericfrom substance groups, andcrates/io/src/chemistry/molfile/generic.rswrites V3000 generic records. crates/io/src/engine/native_import.rsadds the generic notice to imports that contain generic structures.
NMR prediction
Section titled “NMR prediction”Properties → Molecular properties → Predict NMR…, or Predict → NMR in the inspector, predicts ¹H or ¹³C shifts for one molecule from an offline index. NMR prediction describes the method and its limits.
| Layer | Files | Job |
|---|---|---|
| Encoder | crates/chemistry/src/nmr.rs |
Versioned HOSE-style spheres (reshiki-hose-v1) from radius 4 down to 2, median lookup, and a 3-second work deadline (Environment::deadline) |
| Adapter and index | crates/io/src/nmr.rs |
prepare (one whole molecule, at most 128 atoms), fingerprint, the bundled data/nmr/index.tsv, predict and TSV export |
| Dataset | crates/io/src/nmr/dataset.rs, crates/io/examples/nmr_index.rs |
The nmrshiftdb2 transform that builds the index offline. The app never calls it; scripts/regenerate_nmr.py runs it. |
| Job | src/app/nmr/worker.rs |
One prediction at a time per process, on a blocking task with a 6-second outer timeout |
| Panel | src/app/nmr.rs, src/app/nmr/ |
Per-tab state, the floating palette, the docked report, the shift plot, styles and accessible text |
| Labels | src/canvas/nmr.rs |
Session-only atom and shift labels; they never enter the document, the scene or exports |
Results are gated by chemical identity. nmr::fingerprint hashes the stored chemistry and stable IDs of the molecule: atoms with their element, charge, isotope, radical, hydrogen, aromatic and stereo fields, and bonds with their order, stereo and display class. Position, projection paint, labels, atom-map numbers and collapsed abbreviations do not change it, and generic dependencies are refused. A job key holds the request serial, file epoch, atom IDs and fingerprint. A finished job is accepted only for the pending key, and only while the file epoch and fingerprint still match. After every change, including Undo and Redo, the app checks the pending job and the shown report again; a mismatch clears both and shows “Molecular chemistry changed; run prediction again”.
Chemical names
Section titled “Chemical names”Chemical names turn a name into a structure (Import → Name) and a structure into a name (Show chemical name and the Chemical names… inspector), offline. Chemical names describes the behavior and its limits.
- Structure to name. The external
nazuchem-s2nengine began as a port of openclatura 0.4.3 (MIT), extended in ReShiki, with its rule tables and source provenance retained in nazuchem.src/naming.rsre-exports it asnaming::openclatura. The engine reads canonical SMILES throughnazuchem-kernel(parsing, kekulization, rings, stereo perception, CIP). The worker’sgenerateoperation uses protocol 7 and retains its SMILES input andname_smiles_with_stylecall, with ReShiki’s budget under the profileopenclatura 0.4.3 Rust port. It returns the name with its preferred IUPAC name status, which the engine marks at each rule that departs from, or has not been checked against, the preferred name.src/naming/s2n_harness.rsmeasures it; see the harness. - OPSIN boundary.
src/naming/worker.rscallsnazuchem-n2swith ReShiki budgets and separate strict-import and generated-name verification options.Structure::to_irsupplies the typednazuchem_ir::Moleculein Parse and Verify replies, replacing the semantic CXSMILES, locant-array and axis side channels. This IR boundary uses protocol 7 (the CXSMILES worker it replaced used protocol 6). The worker reports the protocol, port version, upstream commit, resource fingerprint and options with every parse, and the client rejects any mismatch. Pythondrawing::from_nameretains its existing semantic CXSMILES path.src/naming/catalog.rsreads only the parser’s upstream version and commit. - Direct IR import.
crates/model/src/molecule_names/ir.rsconverts the IR to a prepared ReShiki chemistryState, retaining source atom and bond indices, explicit hydrogen vertices, hydrogen counts, radicals and supported stereo. The name-import projection applies native hydrogen removal and retains the surviving atoms’ IR identities. Canonical and display SMILES preserve typed E/Z configurations through hydrogen removal and across disconnected fragments.local.rsderives canonical identity from that state and projects parent locants throughMolecule::displayed_locants(), the surviving identity map and a separate writer traversal without private axial markers. Name-import records carry the prepared graph intoRequest::import_prepared, so the native preview and layout use the converted graph directly. SMILES remains the editable display/export view; edits to that text use the ordinary text-import route. Strict import still rejects radicals and relative configurations, while generated-name verification retains its existing allowances and identity gate. - Worker client.
src/naming/client.rsruns each request in a fresh--naming-workerchild under the naming worker limits.local.rsparses a name.reverse.rsgenerates a name, parses it again in a second worker and returns it only when the result has the same canonical identity. Each direction runs one request at a time. - Canonical identity.
naming::canonical_smilesdelegates tonazuchem-s2n, using the pure identity helpers innazuchem-kernel. Caption identity incrates/model/src/molecule_names.rsretains the equivalent implementation over ReShiki’s chemistry state;identity_parity.rschecks both implementations together. The identity is canonical isomeric SMILES with isotope, charge and specified stereo, without atom maps or atom order. Both paths useMAX_NAMING_ATOMS(512) andMAX_SMILES_BYTES(32,768) and keep their own error text. The alias catalog is keyed by the same identity. - Alias catalog.
src/naming/catalog.rsholds the bundled offline alias index:catalog/data.json, with its pinned provenance inprovenance.jsonand its sources inREADME.md. It is keyed bynaming::canonical_smiles, loaded once throughOnceLockand fails closed; see runtime safety for its bounds. The data terms (OPSIN MIT, ChEBI CC BY 4.0) are inlicenses/name-aliases/and listed inNOTICE. - App.
src/app/naming.rsandsrc/app/naming/hold the inspector, the Import dock name field and name captions.src/app/naming/aliases.rsholds the alias and generated-name controls that Import → Name → Details and the Chemical names inspector share.
Agent API (experimental)
Section titled “Agent API (experimental)”The agent API is in ReShiki 0.12.0 and later. reshiki --mcp serves the agent API to an MCP client over standard input and output, and reshiki --cli runs the same operations once from the command line. Both are modes of the single reshiki executable, chosen in src/main.rs after the worker modes and before Office registration and the GUI. Only a first argument of exactly --mcp or --cli selects them.
flowchart LR Client[MCP client] -- stdin / stdout --> Framing[reshiki-mcp framing: lines, admission, cancellation] Framing --> Protocol[rmcp protocol and ToolHost bridge] CLI[reshiki --cli] --> Host Protocol --> Host[ToolHost: HeadlessHost; AppHost in P2] Host --> Ops[ops: executor, session documents, operations] Ops --> Engine[LocalEngine and export] Ops --> Access[access: folder grants] Access --> Dirs[Granted directory handles]- The framing reads one bounded JSON-RPC message per line on its own thread, answers malformed lines itself, admits requests and stops reading while too many are outstanding. Its writer drops the responses of cancelled requests.
- The protocol layer pins rmcp 3.5.1 behind ReShiki’s own transport. It serves MCP 2026-07-28, 2025-11-25 and 2025-06-18 and maps operation results to MCP content:
structuredContent, images and inline embedded resources. HeadlessHostowns session documents in memory. Its executor bounds concurrency, queueing and deadlines; every edit is one atomic store commit. P2 adds anAppHostfor drawings open in the app.- Operations call the same
LocalEngine, composition and export code as the app. Onlyfile_openandfile_savetouch files, throughaccess, which resolves each request against directory handles opened once at startup. reshiki --cliruns each command on an in-processHeadlessHostthrough the sameToolHost::call, and reads and writes its command-line paths with the user’s own permissions.src/cli/commands.rsholds one table of commands and their options. It drives the parser insrc/cli/args.rs, the help text andreshiki --cli help --json.src/cli/reference.rsrenders the agent reference from the help, the tool catalog and the schemas (cargo run --example agent_reference -- docs/agent-reference.md), and a test fails when the committed page is out of date.src/cli/skill.rswrites the Agent Skill;src/cli/io.rsis the only module insrc/clithat touches the filesystem.- The experimental API stays out of the published Rust API:
reshiki_agent::opsis#[doc(hidden)]andreshiki-mcpis an unpublished crate.
Budgets, containment and residual risks are recorded in runtime safety.