# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Purpose

Wraps the PerkinElmer **ChemDrawJS** molecule sketcher so Datagrok users can pick it alongside other sketchers (OpenChemLib, Ketcher) wherever `grok.chem.Sketcher` is used. The package registers a `moleculeSketcher` function whose widget mounts ChemDrawJS into a `<div>` and bridges structure formats (SMILES, MOL V2000, MOL V3000, SMARTS) between the sketcher and Datagrok's `DG.chem` notations.

Auth / credentials: ChemDrawJS is a licensed product. The license XML URL is configured through a single package property, `LicensePath`; without it `init()` renders a "Licence is not available" message and the sketcher never loads. There is no runtime credential handling beyond passing `licenseUrl` to the ChemDrawJS attach call.

## Architecture

Everything meaningful lives in `src/package.ts` (one file, ~215 lines). The `chemdraw/chemdrawweb/` folder holds vendored PerkinElmer assets shipped to clients as `sources` in `package.json` — **do not** edit them. The global `perkinelmer.ChemdrawWebManager` comes from `chemdrawweb.js`.

- **`src/package.ts`** — the whole package. Registers `initChemDraw` (`//tags: init`) and `chemDrawSketcher` (`//tags: moleculeSketcher`, returns `widget sketcher`). Defines `class ChemDrawSketcher extends grok.chem.SketcherBase`, which calls `perkinelmer.ChemdrawWebManager.attach({ element, callback, licenseUrl })`, wires the sketcher's `setContentChangedHandler`, caches the four structure representations (`_smiles`, `_molV2000`, `_molV3000`, `_smarts`), and implements the getters/setters the base class requires. A per-instance `initialized` RxJS `Subject<boolean>` fires once the attach callback returns the `chemdraw` object; `init()` awaits that subject before returning.
- **`src/package-test.ts`** — empty file, reserved for the `grok test` harness. The webpack config still builds `package-test.js` from it, so don't delete it without also removing the entry in `webpack.config.js`.
- **`css/style.css`** — one rule: the `.license-not-available` message shown when `LicensePath` is blank.

### The four-representation cache and `explicitMol`

Every setter (`smiles`, `molFile`, `molV3000`, `smarts`) clears the other three private fields and stores `explicitMol = { notation, value }`. The matching getter short-circuits to `explicitMol.value` when its notation matches, so a value set through the setter is returned **verbatim** without round-tripping through `DG.chem.convert`. This exists because ChemDrawJS's own `getSMILES` / `getMOL` / `getMOLV3000` can re-canonicalize the structure and lose caller intent (e.g. atom ordering, query features).

`moleculeChangeHandler()` resets `explicitMol = null` only on a **real user edit**. Each setter sets `_importing = true` before calling `loadSMILES` / `loadMOL`; the handler's first entry flips it back to `false` and skips the reset. The guard `this._smiles !== null || this._molV2000 !== null || ...` prevents the reset on the very first change after construction when nothing is cached yet. When adding a new import path, set `_importing = true` before calling the underlying load.

### Dialog / popup z-index hack

`init()` walks up from `host.root` to find a `.d4-dialog` or `.d4-popup-host` ancestor, then injects a `<style>` tag raising `.cdd.cdd-toolbar-popup-container` above it. Without this, ChemDrawJS toolbar pop-overs render *behind* the Datagrok dialog they're hosted in. If you add new ChemDrawJS-owned floating UI, it may need similar treatment.

## Glossary — domain concepts → code

| Concept | Code type(s) | One-line meaning | Key relationships |
|---|---|---|---|
| ChemDrawJS | external JS library, vendored at `chemdraw/chemdrawweb/` | PerkinElmer's in-browser molecule editor | Accessed through the global `perkinelmer.ChemdrawWebManager.attach(...)`; the returned sketcher exposes `loadSMILES`, `loadMOL`, `getSMILES`, `getMOL`, `getMOLV3000`, `setContentChangedHandler` |
| `explicitMol` | `{ notation, value }` on `SketcherBase` | Cached exactly-as-set value; short-circuits the matching getter so the sketcher's own re-canonicalization is bypassed | Reset to `null` on real user edits via `moleculeChangeHandler`; `_importing` distinguishes setter-triggered content changes from edits |
| `_importing` | `boolean` on `ChemDrawSketcher` | Marks that the next `moleculeChangeHandler` firing was caused by a programmatic load, not a user edit | Set in setters, cleared in the handler's first branch |
| molV2000 / molV3000 | `_molV2000`, `_molV3000` | Two MDL MOL-file variants; V3000 handles larger/complex structures V2000 can't | `DG.chem.Notation.MolBlock` ↔ V2000; `DG.chem.Notation.V3KMolBlock` ↔ V3000 |
| SMARTS vs SMILES | `_smarts`, `_smiles` | SMILES is a compact structure string; SMARTS adds query features | `smiles` getter calls `DG.chem.smilesFromSmartsWarning()` when only `_smarts` is available, because the conversion is lossy |
| `LicensePath` | `package.json` property, read into `_properties.LicensePath` | URL of the ChemDrawJS license XML | Passed as `licenseUrl` to `ChemdrawWebManager.attach`; empty string = no-op, render "Licence is not available" |

## Conventions specific to this package

- **Never edit `chemdraw/chemdrawweb/`.** Vendored PerkinElmer assets shipped to clients verbatim via `package.json` `sources`. Version bumps come from PerkinElmer drops, not hand edits.
- **`_properties` is populated in `initChemDraw`, not the constructor.** Any new code that reads package properties (today: `LicensePath`) must run after `initChemDraw()` has awaited `_package.getProperties()`, or guard against `undefined`.
- **`perkinelmer` is an untyped global.** Current usage keeps a single `//@ts-ignore` at the `ChemdrawWebManager.attach` call site; do not add a broader `declare` block — isolate the typelessness to one line.
- **Setters MUST set `_importing = true` before calling `loadSMILES` / `loadMOL`.** Otherwise the subsequent `moleculeChangeHandler` firing will clear `explicitMol` and drop the caller-supplied exact value. See `src/package.ts:199`.
- **Asymmetric SMARTS access.** `get smarts` does not exist; read SMARTS through the async `getSmarts()` method (it calls `_sketcher.getMOL` and converts via `DG.chem.convert`). `set smarts` is synchronous and goes through `convertAndSetSmarts`. Don't "fix" this symmetry without checking `SketcherBase` callers.
- **No ChemAxon-style web services.** Unlike the Marvin package, ChemDraw has no `MolConvertPath` / `Clean2dPath` remoting; all format conversion is local via `DG.chem.convert`. Do not introduce a `setServices`-style layer unless PerkinElmer adds one upstream.
