ChemDraft
AGENTS.md66 starsChanged 12 days agoApache-2.0View on GitHub
ChemDraft is a lightweight, open-source chemical drawing application with a plugin architecture. The core stays small, stable, testable, legally clean, and focused on drawing. This file governs every AI agent and human contributor, on every branch. It is repo-wide: scope for one slice belongs in docs/plans/PLANS.md, never here. ChemDraft ships for macOS and Windows together, from the same commit, under one version number. Every code change must work on both. A change that works on one platform is not finished,…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- chemdraftSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
15 rules. About 0 are common, 10 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Every code change must work on both.
- Distinctive: line 43
Verify on both, or say which you did not: When a change touches native code, windows, menus, shortcuts, focus, the clipboard, file …
- Very specific: line 56
Before editing implementation files, read docs/plans/PLANS.md, this file, README.md, package.json, and pnpm-workspace.yaml.
- Very specific: line 138
Never bypass the fail-closed install gates: checksum and CRC, manifest validation, apiVersion and worker handshake, path-traversal guards …
- Distinctive: line 214
The bridge is an automation surface, not a product API: Keep it gated; add no new way to turn it on.
- Distinctive: line 253
Every conversion preserves atom identity, bond order, formal charge, isotopes, radicals, stereochemistry, superatoms and R-group display …
- Distinctive: line 303
Every menu item, toolbar button, palette tool, shortcut, and plugin action is backed by a command in the shared registry.
- Very specific: line 314
No permanently disabled buttons: unimplemented tools are removed until their slice lands, and the customize gallery excludes them.
- Very specific: line 335
Never show fake chemistry: workspace objects are real chem-core objects or an honest disabled state.
- Distinctive: line 337
Tools and dialogs float (owner rule, 2026-10-14): Every tool palette, panel and dialog is its own native window that the user can move …
- Distinctive: line 358
Performance: Do not load heavy chemistry or recognition engines at startup unless required.
- Distinctive: line 359
Prefer loading RDKit, recognition, plugin panels, and optional importers and exporters lazily.
- Distinctive: line 364
Every meaningful change adds or updates tests in its area (migrations, permissions, chemistry and format fixtures, canvas/export parity, …
- Very specific: line 399
Every build shows <dir> [<branch>] in the window title, the build stamp, and the run-app banner, driven by CHEMDRAFT_WORKTREE_LABEL …
- Distinctive: line 419
Keep tasks narrow and never invent a dependency's capabilities.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Agent Instructions for ChemDraft
ChemDraft is a lightweight, open-source chemical drawing application with a plugin architecture. The
core stays small, stable, testable, legally clean, and focused on drawing. This file governs every AI
agent and human contributor, on every branch. It is repo-wide: scope for one slice belongs in
docs/plans/PLANS.md, never here.
1. Both platforms, every change
ChemDraft ships for macOS and Windows together, from the same commit, under one version number. Every code change must work on both. A change that works on one platform is not finished, and "the other platform is a follow-up" is not an acceptable state to merge.
- Shared code by default. Branch on the platform only where the operating system forces it, and
write both sides in the same change. Rust: every
#[cfg(target_os = "macos")]gets its#[cfg(not(target_os = "macos"))](orcfg(windows)) side, and clippy runs with-D warningson both, so code dead on one side fails there. TypeScript: take the platform as a parameter that defaults to detection (ShortcutPlatform/detectDesktopShortcutPlatform()inkeyboardShortcuts.ts), so one test host can exercise both branches. Tauri: platform-only capabilities carry"platforms": [...]; Windows-only config lives intauri.windows.conf.json. - Known differences you must respect:
- App origin:
tauri://localhoston macOS,http://tauri.localhoston Windows (WebView2). Anything served same-origin, such as installed plugins, must accept both (installed_plugins.rs). - Menus: app-wide on macOS; on Windows only the document window carries one (
install_app_menu). Attaching a menu to palettes or popovers crashed Windows at startup and quit. - Utility windows (palettes, popovers, tooltips) are built
focused(false)and owned by the document window, so on Windows they never steal focus or show on the taskbar. Place them by the destination monitor's scale, and never save a frame while minimized. - Shortcuts:
CmdOrCtrl, a Ctrl+Y redo alternate off macOS, Mac glyph labels only on macOS, and F5/Ctrl+R must never reload the webview (a reload discards the document). - Quitting: on Windows, closing the document window quits after flushing the session.
- Updates: Sparkle and
appcast.xmlon macOS;tauri-plugin-updaterandlatest.jsonon Windows. - Clipboard: Windows formats are handled in
windows_clipboard.rs(UTF-16, CRLF, Office formats). - Files: Windows registers
.chemdraftonly; it must never take over.cdxml. - Paths and tools: backslashes, 8.3 temp paths, no bash, no
zip, symlinks need privileges. UsepathAPIs, never hard-coded/. New repo scripts are Node (scripts/*.mjs), not bash, unless the job is macOS-only by nature (signing, notarizing, Sparkle,./run-app). - Native binaries are per platform: the 3D sidecar is committed; the OPSIN Java runtime is built on
each host (
scripts/build-opsin-runtime.mjs), never committed.
- App origin:
- CI runs three hosts: Linux (types, tests, web build), macOS (Rust fmt, clippy, tests), and Windows (types, tests, web build, Rust fmt, clippy, tests). All of them, Windows included, must pass to merge.
- Verify on both, or say which you did not. When a change touches native code, windows, menus, shortcuts, focus, the clipboard, file input/output, paths, the updater, or plugin install and serving, check it on both platforms. If you can run only one, the report must name the other as unverified and say exactly what needs checking there. Never report a platform as verified that you did not run.
- Releases are joint: the macOS DMG and the Windows NSIS installer are built from the same commit,
the GitHub release body has
## macOSand## Windowssections, and neither platform gets a version the other does not. Seedocs/releasing/. The Windows installer is built only by.github/workflows/windows-release.ymlon a GitHub-hosted runner (owner rule, 2026-10-22), never on a local machine or VM; the Windows session tests the installer CI built.
2. Orientation
Before editing implementation files, read docs/plans/PLANS.md, this file, README.md, package.json, and
pnpm-workspace.yaml. If the work touches a package, read its README too.
docs/plans/PLANS.mdis the slice in flight, and binds unless the user gives newer instructions. Keep edits to its files and behaviors. At closeout the slice moves todocs/shipped/README.md, sodocs/plans/PLANS.mdnever becomes a changelog.docs/plans/PLAN.mdis the product charter: whether a thing should be built at all. Read it only when scoping a feature (§3, §4, §19), deciding core versus plugin (§5 non-goals, §21), adding a dependency or touching licensing (§15), judging release readiness (§1.1, §4, §19), or changing a user-facing surface (§6.15).- Other references:
docs/architecture/(subsystems),docs/plugin-architecture/AUTHORING.md(plugin authors),docs/shipped/(readselection-policy-refactor.mdbefore touching selection or hit testing), and/Users/jeremiahgassensmith/programming/.notarybefore any signing or notarizing. - Names. The app is ChemDraft. "MolScribe" names only the external recognition project and the optional MolScribe OCSR plugin. Public text (website, release notes, PR titles and bodies, commit messages) never names a commercial competitor or compares ChemDraft to one; file-format names such as CDXML are fine.
- Landing work. Work only in the worktree checked out for your branch.
mainis protected: changes land by pull request with green CI. Several agents may share a checkout, so check the branch before committing and stage explicit paths.
3. This phase: plugins and agents
This phase grows two surfaces: what plugins can do inside ChemDraft, and what agents (AI or scripted callers) can do with it. Both hand ChemDraft's power to code the core does not control, so they share these rules:
- One command system. Everything a plugin or agent can do is a registered command or a typed host capability; no side doors into React state, the DOM, or private functions.
- Declared, least privilege. The host grants only declared permissions; nothing dangerous runs without explicit user action.
- Changes go through patches, one labelled undo entry each, with what changed left selected.
- Chemistry is never silently changed (§7), in the CLI, MCP, and plugins as on the canvas.
- Typed, versioned contracts. Additive API changes bump the patch, breaking ones the minor (§4.1).
- Same results on both platforms (§1), headless paths and spawned processes included.
- Ship it whole: permission gate, tests, authoring docs, and an example where one helps, in the same change.
4. Plugins
4.1 Contract and permissions
A plugin imports only @chemdraft/plugin-api (plus ordinary npm packages), never another
@chemdraft/* package; tools/plugin-extract/boundary.test.ts enforces this. A manifest declares
id, name, version, apiVersion, entry, and permissions. Plugins may contribute commands,
menus, panels, toolbar buttons, inspectors, templates, importers, exporters, analyzers, transformers,
and recognizers.
PluginApiVersion is 0.1.20. For a 0.x version the minor is the compatibility boundary, so additive
methods bump the patch and a plugin declares the caret version it needs. Record each bump in the
plugin-api header comment and AUTHORING.md.
Permission names are defined in plugin-api. The dangerous ones are filesystem.write,
network.fetch, native.execute, model.load, model.download, clipboard.read, document.write,
and image.read beyond a user-chosen image. A plugin never receives a capability it did not declare,
and any capability that spawns a process also requires native.execute (OPSIN name→structure needs
it beside chemistry.compute).
Naming: commands plugin.<pluginName>.<action> (schema-enforced for toolset contributions), menus
menu.<pluginName>.<action>, panels panel.<pluginName>.<name>, analyzers
analyzer.<pluginName>.<name>.
4.2 Runtime and panels
- One persistent plugin runtime (
plugins/createPluginRuntime.ts, owned byusePluginRuntime), created once and fed document and selection through callbacks. Never recreate it on a document, selection, viewport, or undo change. - Plugin commands share the core
CommandRegistry; apluginIdmarks them. Every registration path goes through the runtime'sregisterPlugin/unregisterPlugin, never the bare host, so toolbar contributions keep theirui.toolbargate and whole-plugin rollback. - Analyzers run in per-plugin module Workers (
PluginWorkerBridge);terminate()is total teardown;worker.format: "es"invite.config.tsis load-bearing. - Panels are declarative
PluginPanelReportdata drawn by one renderer,PluginReportRenderer; an unknown section kind is never dropped. Each desktop panel is its own native window, and closing it notifies the plugin.
4.3 Installing, serving, and updating
- Installed packages are staged under
$APPDATA/installed-plugins/and served same-origin: the app pre-empts its own scheme (installed_plugins.rs) and Vite mirrors it with/installed-plugins/middleware. Keep both hooks; never add a plugin scheme (a new origin stops packages loading). - Never bypass the fail-closed install gates: checksum and CRC, manifest validation,
apiVersionand worker handshake, path-traversal guards in TypeScript and Rust. - Updates come from allowlisted GitHub releases, SHA-256-verified, then staged, swapped, or rolled
back.
capabilities/plugin-updates.jsonlists every release URL exactly, the.zip.sha256included;pluginUpdates.test.tsguards it. - Official plugins (NMR predictor, MolScribe OCSR) live in their own repositories and the host catalog
(
plugins/pluginUpdates.ts); their menu items exist only once installed.
4.4 Writing to the document
Plugins mutate documents only through patches. A plugin with document.write calls
documents.applyPatch only while one of its own commands is running. It may insert objects, and may
change or remove only objects selected when that command started plus objects it inserted during
that command. Writes land directly, leaving what changed selected. The assistant transaction groups
writes into one labelled undo entry per AI request; for other plugins, each documents.applyPatch
call creates its own labelled undo entry (including a batch patch). Undo and redo restore recorded
states exactly; they never re-run a plugin or an AI. Proposal review stays mandatory for image recognition
results (MolScribe); other plugin writes need no review window. Reports are for failures; success
needs no window.
4.5 Structure recognition (MolScribe OCSR)
ChemDraft owns the local recognition engine, its install UI, and the native boundary. The plugin
(org.chemdraft.ocsr.molscribe) receives only recognition.recognizeStructure, for an image the host
returned during the same command; it declares image.read, ml.inference, model.load, and
native.execute, and never model.download or document.write. The engine
installs only on explicit user action and runs only on the computer. Results carry SMILES, molfile,
confidence, candidates, and warnings (confidence, stereo, charge, abbreviations, invalid structure,
missing model); the source image stays unless the user deletes it. Tests use mocked recognitions; never
commit checkpoints or present a recognition as certain.
4.6 NMR claims (absolute)
The shipped backend is the OCL-native provider: HOSE-fragment lookup over statistics from NMRShiftDB2 experimental assignments. Never describe it as fixture-backed or synthetic (fixtures are test-only and labelled synthetic). Accuracy figures stay checksum-gated to the benchmarked corpus. ¹H multiplicity and J are first-order estimates, labelled so. Stick height is equivalent nuclei, not integration; lineshape and field are simulation parameters. Never fabricate a shift for an unmatched environment; partial results carry warnings. No calibrated confidence percentages, only honest tiers.
4.7 Licensing and redistribution
- The core is Apache-2.0; example plugins and the two SDK packages are MIT. Changing either is the
owner's call.
NOTICEships with every distribution; a new vendored binary adds its row there and indocs/architecture/dependency-inventory.mdin the same change (InChI 1.07.3 terms are still unconfirmed and block public redistribution). - Code licence and data licence are separate claims. A plugin bundling data declares its code
licenseand adataLicensesentry per dataset;tools/plugin-extract/gates.tsrefuses a dataset covered by a code licence alone. The NMR predictor's database carries nmrshiftdb2's ODbL-derived terms: never call that package "MIT".
5. Agentic surfaces
| Surface | Where | Contract |
| Headless CLI | packages/chemdraft-cli | pnpm -s chemdraft <render|document|render-document|grid|reaction|analyze|name|stereo|nmr|export>; JSON Lines on stdout, progress on stderr; exit 0 all ok, 1 any job failed, 2 bad input |
| MCP server | packages/chemdraft-mcp | Local stdio server calling the CLI modules in-process; a fresh per-call output directory; 5 MB per returned payload |
| Agent skill | skills/chemdraft | One skill folder in the open Agent Skills format, kept in step with the CLI and MCP by tools/agent-skill/chemdraftSkill.test.ts |
| In-app bridge | apps/desktop/src/agentBridge.ts | window.__CHEMDRAFT_AGENT__; off unless CHEMDRAFT_AGENT_BRIDGE=1 or --chemdraft-agent-bridge (desktop) or ?agentBridge=1 or its localStorage flag (web build) |
| Shared logic | packages/document-workflow-core | The pure document functions the app, CLI, and MCP all use |
Rules:
- One implementation. The CLI and MCP import document logic from
document-workflow-core(or another package), never fromapps/;importBoundary.test.tsenforces this. A new agent tool calls the same function the app calls, so the canvas and the agent cannot disagree. - Machine-readable and quiet. stdout carries only results (hence
pnpm -s; a script banner corrupts JSON Lines and MCP framing). Failures name what failed and why, per job. - Agent input is untrusted input. Validate it with the same limits as user input (the CLI caps input at 5000 characters, 500 heavy atoms, and 500 jobs per batch, and rejects unsafe output names). Exports check that the canonical SMILES written equals the input's and fail otherwise.
- Same honesty as the UI. Numbers keep their method contract, status, and intervals (§8); estimates stay labelled estimates; unpreservable radicals or isotopes are refused, not changed.
- Plugin code loaded headlessly is trusted explicitly. The CLI loads an external plugin (such as the NMR predictor) only when its directory is listed in the user's trust file; an environment variable alone never makes an unlisted directory load.
- The bridge is an automation surface, not a product API. Keep it gated; add no new way to turn it on. Its events are synthetic and skip browser default actions such as moving focus, so focus- and blur-sensitive behavior also needs a real-event test.
- Agent edits follow §4.4.
- New CLI commands and MCP tools ship with README entries, an entry in
skills/chemdraft, and tests, and run on both platforms (§1).
6. Package boundaries
Engines stay behind adapters; chem-core is the native document model; no package owns UI unless its
row says so.
| Package | Owns | Must never |
chem-core | Types, Zod schemas, patches, serialization, migrations, validation, every native object, pages, style presets | UI, Ketcher, Tauri, filesystem, plugin loading |
editor-adapter, ketcher-adapter | Abstract editor contract; Ketcher loading and wrappers | Own document state, expose Ketcher internals, fake unsupported objects |
plugin-api | Public plugin types, manifest, permissions, contexts | App code, document mutation |
plugin-host | Validation, permission enforcement, commands, loading, storage scoping, lifecycle, patch review | Grant undeclared permissions, run native code or download models without approval |
cdx-compat | CDXML read/write, best-effort CDX read, unknown-object preservation | Become the native model, depend on GPL code, claim perfect compatibility |
clipboard-adapter | Clipboard format detection and conversion, with warnings | Silent lossy conversion |
layout-engine | Align, rotate, snap, bond-length normalization, all molecule rendering math (§9), and the pure atom-label grammar (what a label's text means) | Change chemical identity |
art-engine | Art visual plans: strokes, markers, arrow geometry, boolean ops | Touch molecules, own documents, copy molecule rendering math |
export-engine | SVG, PDF, CDXML writers with warnings | Report a format done that isExportFormatImplemented denies; diverge from the canvas |
shortcut-engine, toolset-registry | Shortcut registry and conflicts; toolset schemas, overrides, menu models, command-ID validation | Bind actions only in click handlers; chemistry, permissions, windows |
template-library, style-compat | Original templates and abbreviations; .cds style import into native presets | Copy proprietary templates (studying them for conventions is fine, §12); parse .cds in UI; commit user .cds files; claim a partial import worked |
viewport-engine | Coordinate, zoom, pan, ruler math | Document mutation, rendering |
chemistry-adapter, ocl-adapter | Engine-neutral contract (incl. 3D conformers); OpenChemLib depiction, stereo, conformers | Concrete engines in the contract; collapse an unrepresentable bond order (report aromatic/unknown) |
rdkit-adapter | Real RDKit analysis and ETKDGv3 conformers on the vendored custom MinimalLib WASM (vendor/BUILD.md) | Load at startup unless required (§13); vendor another RDKit build without its own decision |
isospec-adapter | Vendored, unpatched IsoSpec WASM, pins, lookups read from the binary; rdkit-adapter depends on it, never the reverse | Patch IsoSpec; retype its abundance table; build results (rdkit-adapter/src/envelope.ts does); add a raw-array wrapper that skips length checks (IsoSpec reads past the heap silently). Disclose its ¹³C abundance (0.82% above CIAAW) wherever a derived number shows |
analysis-core | Pure property-suite contracts: interpretations, classification, results, methods, provenance | Import an engine, derive chemistry, branch on derivation/claim |
engine3d-api | The versioned app↔sidecar protocol | Change shape without bumping Engine3DProtocolVersion |
document-workflow-core | Pure document builders shared by app, CLI, and MCP | React, Tauri, DOM, apps/, app state, loading an engine (callers inject it), a second copy of anything moved here |
editor-shell, test-utils, fixtures, ui-kit | Unused types; test helpers; test fixtures; original icons and controls | Grow speculatively; ship in production; proprietary or unredistributable content; chemistry in UI |
examples/plugins/: mass-fragment-demo is the working, deliberately non-NMR example (keep it free of
spectroscopy); advanced-style-pack and journal-style-pack are README-only placeholders, never
described as shipped.
7. Chemistry invariants
Every conversion preserves atom identity, bond order, formal charge, isotopes, radicals, stereochemistry, superatoms and R-group display where represented, reaction roles and components, coordinates where applicable, and mechanism annotations (always editable objects, never opaque SVG). An operation that cannot preserve chemical meaning must warn or fail, never degrade quietly. Toolbar, inspector, style, and layout work never change chemistry. Tests compare canonical SMILES, formula, charge, stereo, atom and bond counts, reaction component counts, and coordinates within tolerance.
8. Analysis and prediction claims
The plan of record is docs/shipped/analyzers-property-prediction-suite.md. A bare "§n" in the
analysis source, or a stale "docs/plans/PLANS.md §n" there, refers to that document.
- One parse, many named interpretations. Parse once through RDKit. Composition, charge, mass, and
isotopes describe what the user drew. A method wanting a desalted or neutral form gets a derived
interpretation with a
Transformationledger, never a silent substitute; sodium benzoate never becomes benzoic acid. The active interpretation is visible and changeable, and per-atom results map back to drawn atoms. - Enums display, flags decide. Code branches on
ClassificationFlags, never onderivationorclaim; add no fourth axis. - Declining is a feature. A method outside its parameterisation returns
unsupported, not a fallback number. Runtime failures map toAnalysisStatusandapplicability, never prose. "Unavailable" and "not asked for" must look different. - Every number carries a method contract (implementation and version, interpretation, units, conventions, scope, declining conditions). Conventions travel on the result; a "see X" note must point at a section the report contains. Detect engine capabilities by value, not arity.
- No second interpretation engine. Formula, charge, and mass come from RDKit; never re-decide valence, hydrogens, or aromaticity.
- Validation is conditional on a partition. State the partition with every coverage or accuracy figure ("external development set", never a bare percentage). A fix stratified on one partition (element, scaffold) can still leak through the next (distribution, molecular family): name and measure that one before claiming success. Write abstention rules before opening the set that judges them.
9. Shared rendering math and Spin 3D
- Molecule rendering math (bond geometry, double and triple bond gaps, wedges and hashes, atom-label
content and layout, stroke widths, depth cues) lives only in
packages/layout-engine. App code, including the spin overlay, imports it and never keeps its own copy, even temporarily: two copies once diverged silently. Export a package-internal helper rather than copying it; give app code with different behavior a different name. - Spin 3D shares one
ScreenPlacementcontract across the live overlay, flatten and release, reopen, and every drag and typed rotation.flattenSpunMolecule(..., { placement })must matchprojectSpin. Projection helpers stay inapps/desktop/src/interaction/, flattening indocumentWorkflow. Changes here prove themselves withspin3dModel,spinFlatten,spinFlattenStereo,flattenRoundTrip,spinOverlay, androtation3dtests. Force-field work followsdocs/plans/spin3d-forcefields.md.
10. Commands and toolbars
- Every menu item, toolbar button, palette tool, shortcut, and plugin action is backed by a command in
the shared registry; never wire major behavior only inside a click handler. Command IDs are
value-encoded with factory helpers; no generic
*.setcommands with hidden parameters. Do not change command IDs unless the active plan retires or introduces them. - Reuse:
toolsets.tsmaps registry items to palette models,ToolPalette.tsxrenders the inline fallback,PalettePopoverWindow.tsxrenders native flyouts through the request/snapshot/window-manager path, which stays. - Toolbar contract: inline submenu commands invoke exactly once per click; disabled ones never
invoke; an item with a disabled primary but enabled submenu still opens it; an item with nothing
enabled is disabled. Inline submenus carry owner/menu/menuitem ARIA; native flyout owners may say
aria-haspopup="menu"but never pointaria-controlsat missing DOM. No invented tooltip filler. No permanently disabled buttons: unimplemented tools are removed until their slice lands, and the customize gallery excludes them.disabledReasonis only for transient, state-dependent unavailability, and such commands always carry one.
11. File-format compatibility
Compatibility is fixture-driven and never claimed beyond fixture coverage. Tier A (supported): atoms, bonds, coordinates, charges, isotopes, radicals, wedges, E/Z geometry, superatoms, basic R-groups, text, simple arrows, plus signs, basic brackets and styles. Tier B (only with fixtures): R-group logic, S-groups, polymers, atom lists, mapping, equilibrium and retrosynthesis arrows. Tier C (preserve or approximate, never claim): complex graphics, images, unusual fonts, proprietary style state. Preserve unknown objects, or warn. CDXML comes before broad CDX writing.
12. UI
Familiar to chemists, original in every asset: never copy proprietary icons, templates, dialog art,
help or menu text, sample files, command IDs, or trade dress. Studying any reference depiction,
including another program's templates, for conventions and geometry and then drawing our own with our
own code is allowed (owner, 2026-10-28); copying template files, coordinates, icons or art is not.
Menu and dense icon toolbar on top, a dominant page workspace, icon-first palettes owned by the
document window, panels hidden until needed.
Never show fake chemistry: workspace objects are real chem-core objects or an honest disabled state.
Tools and dialogs float (owner rule, 2026-10-14). Every tool palette, panel and dialog is its own native window
that the user can move anywhere, off the document window and onto another monitor; never an overlay fixed inside
the document viewport. Dialogs are owned by the document window (no Windows taskbar entry, §1), open centred over it
the first time and reopen where they were left, and use the shared dialog host
(docs/architecture/native-dialog-host.md). Dialogs are modeless unless an answer is required: the floating
toolbars stay usable beside them and a dialog can come in front of them. Simple confirmations use native message
boxes. Existing in-viewport dialogs are being migrated; add no new ones.
Canvas overlay exception (owner rule, 2026-10-22). One declared canvas overlay per plugin — an icon
that expands into a prompt box with a status line, permission ui.canvas — is the only permitted
in-viewport plugin surface. It is draggable, remembers its place, hides during canvas gestures and in
exports, and takes focus only when clicked. Dialogs, palettes and panels still float as native windows.
13. Security, errors, and performance
- Security. Plugins get no file, network, native, model, clipboard, or remote-recognition access
without the matching permission and user action. Outbound network paths are allowlisted in
src-tauri/capabilities/. Imported files are sanitized; malformed input fails safely. - Errors are specific. Not "Import failed." but "CDXML import failed: unsupported bond display type "WedgeHashBegin" in object b42." Short for users, detailed in logs. Recognition errors distinguish missing model, low confidence, invalid structure, unavailable service, and refused permission.
- Performance. Do not load heavy chemistry or recognition engines at startup unless required. Prefer loading RDKit, recognition, plugin panels, and optional importers and exporters lazily. No heavy dependency for a trivial job.
14. Testing and verification
Every meaningful change adds or updates tests in its area (migrations, permissions, chemistry and
format fixtures, canvas/export parity, command wiring, mocked recognition). A test that cannot be
written yet is explained in the report, with a TODO only if it is specific (TODO(cdx-compat):
preserve rotation once GraphicObject.rotation exists, never TODO: fix later). Test platform
branches from one host by passing the platform in (§1).
The gauntlet, mirroring CI:
pnpm lint # root tsc; stricter than per-package typechecks
pnpm vitest run
pnpm build
git diff --check
cargo fmt --manifest-path apps/desktop/src-tauri/Cargo.toml --check
cargo clippy --all-targets --manifest-path apps/desktop/src-tauri/Cargo.toml -- -D warnings
cargo test --manifest-path apps/desktop/src-tauri/Cargo.tomlScoped scripts when the work enters their area: pnpm build:sdk, pnpm plugin:extract,
pnpm plugin:package, pnpm audit:engine3d-sidecar, pnpm smoke:engine3d-*, and
pnpm smoke:windows-menu-churn (menus or windows). Pointer, hit-testing, or bridge changes also run
the DOM, agent-bridge, and drawing-tool suites. Interactive surfaces get the hands-on pass in
docs/manual-stress-checklist.md on both platforms; add to that list when a slice ships a new
surface, and never replace it with a slice-scoped one.
15. Builds, launching, and the stable app
- Launch from this worktree. macOS:
./run-app(packaged build) or./run-app --dev(Tauri plus Vite hot reload). Windows:pnpm dev, orpnpm --filter @chemdraft/desktop build --bundles nsisfor an installer. An app that was already running, orcargo run, is not proof. Stop other instances from the same checkout first, and report the exact command and the success signal (bundle path, or Vite port plus the launched binary).
15.1 Every build is labelled by its worktree (do not remove)
Every build shows <dir> [<branch>] in the window title, the build stamp, and the run-app banner,
driven by CHEMDRAFT_WORKTREE_LABEL (build.rs re-emits it so cargo recompiles the title). Do not
strip it from run-app, vite.config.ts, lib.rs (main_window_title), or build.rs, and report
the label you saw. Only a stable build (on main, or CHEMDRAFT_STABLE_BUILD=1 through
scripts/desktop-tauri.mjs) is unlabelled, on both platforms.
15.2 The stable app is never replaced (do not remove)
The stable app (/Applications/ChemDraft.app; %LOCALAPPDATA%\ChemDraft on Windows) is built from
main as org.chemdraft.desktop. Branch builds are a different application, ChemDraft (dev) with
the shared identifier org.chemdraft.desktop.dev, one shared app-data folder (settings and installed
plugins), and one shared single-instance lock. Only one dev build runs at a time; quit it before
launching another worktree's build. Stable app data and its lock stay separate.
On macOS run-app moves (never copies) the bundle
into <worktree>/app/. A branch never builds over, renames, or unregisters the stable app.
./run-app --dev runs a bare binary with no bundle identifier, invisible to identifier-scoped tools.
Identify a build by its stamp, never by its window.
16. Closeout and report
Keep tasks narrow and never invent a dependency's capabilities. At closeout:
- Bump
CURRENT_BUILD_STAMPinapps/desktop/src/MainWindow.tsx(CI requires it for any change underapps/desktop/src,src-tauri/src, orpackages). Its suffix names the authoring agent (-opus,-codex,-fable), never the branch. - Launch the new build (§15) and run the gauntlet (§14).
- Move a finished slice from
docs/plans/PLANS.mdtodocs/shipped/README.md. - Report: Summary, Files changed, Tests run, Results (pass or fail, honestly), Platforms (macOS and Windows each verified, or unverified with what remains to check), Known limitations, and One recommended next task.
17. Older section numbers
Comments and docs written before 2026-10-04 cite the old numbers. Cite new sections by number and title, since old and new numbers overlap.
| Old | Now | Old | Now |
| §1 | §2 | §8, §8a | §4.2–§4.7 |
| §2, §3, §9 | §10 | §8b, §9a | §8 |
| §4 | §2, §10, §12 | §8c | §4.7 |
| §5.7, §10 | §7 | §11, §12 | §11, §12 |
| §5.26, §5.27 | §9 | §13, §19, §20 | §14 |
| §6.n (by package) | §6 | §14, §15, §16 | §13 |
| §7 | §4.1, §4.4 | §17, §18, §22 | §16 |
| §21, §21.1, §21.2 | §15, §15.1, §15.2 |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

