Files
OneCAD-Draft/docs/plugin-template-v2/DESIGN.md
T
Felix RiestraandClaude Sonnet 5 bd49bd66d4 docs(plugin): fix inaccuracies and flag real gaps found building a plugin
Found while building an out-of-tree scripting plugin from scratch against
this doc and the actual ocs_plugin_api/host source. Two categories:

Fixed outright (docs/examples didn't match the real code):
- The worked CadModule example returned Vec<RibbonGroup> from
  ribbon_groups(); the real trait requires &[RibbonGroup] - the example
  wouldn't compile. Fixed with the OnceLock pattern the host's own
  benchmark code already uses.
- Reconciled a contradiction between "process isolation removes the need
  for a shared Rust ABI" and the exact-rustc_version requirement described
  a few paragraphs earlier in the same doc.
- The roadmap's "Next" list still had external automation as unstarted;
  --mcp/--serve are real and operational (src/cli.rs, src/mcp.rs).
- Added the missing plugin.toml to docs/plugin-template-v2 (its own
  DESIGN.md already claimed one could be installed).
- Documented that plugin-template's CI floats rust-toolchain@stable, which
  doesn't guarantee matching the host's exact rustc - added acadrust_source
  to both templates' plugin.toml plus the release.yml step that fills it
  from Cargo.lock's resolved acadrust source, alongside the existing
  rustc_version fill.

Flagging for your call, not fixed here (found the behavior, not sure it's
the intended one, prefer to ask than guess at a fix):
- acadrust_compatible()'s ABI gate only runs at all if plugin.toml declares
  acadrust_source - an undeclared field is silently treated as compatible,
  unlike rustc_version (undeclared = incompatible). The same shortcut
  appears in two places: ExternalPlugin::acadrust_compatible
  (src/plugin/external.rs) and the inline check inside fetch_release_info
  (src/plugin/marketplace.rs). If this is deliberate (e.g. back-compat with
  manifests from before the field existed), no action needed beyond a
  comment; if not, both call sites need the same fix.
- The command line still uppercases typed/pasted input even with the `>`
  literal-spaces prefix (Message::CommandInput, src/app/update/mod.rs) -
  `>` only stops a space from auto-submitting the line, it doesn't skip
  `s.to_uppercase()`. A plugin command needing a case-sensitive typed
  argument has no escape today short of automation.
- fetch_release_info (src/plugin/marketplace.rs) returns every release
  under a linked repo with no filter on the manifest's own id field (which
  it already parses to build ReleaseInfo) - a repo hosting more than one
  plugin under tag-namespaced releases surfaces all of them mixed
  together in one plugin's version list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 03:37:54 +02:00

3.0 KiB

plugin-template-v2 — Shared-memory DocumentReader example

This crate is a runnable plugin template that demonstrates the V2 host/plugin contract:

  • Zero-copy reads through [ocs_plugin_api::host::DocumentReader].
  • Validated writes through the existing HostApi RPCs (add_entity, write_record).
  • Read→write round-trips: read an entity handle from the shared document view and attach XDATA to it.

It lives under docs/ (not crates/) because it is documentation/example code rather than a runtime dependency of the host.


Commands

Command Purpose
COUNT_SURVEY_POINTS Count point entities on the SURVEY layer via document_reader().
ADD_SURVEY_POINT Add a new point entity on the SURVEY layer via host.add_entity().
MARK_FIRST_SURVEY_POINT Read the first SURVEY point and mark it with an XDATA record.
COUNT_MARKED_SURVEY_POINTS Count SURVEY points that carry the SURVEYMARK XDATA record.

Design notes

Reads are zero-copy; writes are RPCs

document_reader() returns a read-only view backed by host-owned shared memory (for out-of-process plugins) or by &CadDocument (for in-process plugins). The plugin iterates entities without copying the model.

All mutations still cross the validated RPC boundary. This preserves the host's crash-safety boundary: a buggy plugin cannot corrupt the host document because it never holds a mutable reference to host memory.

Entity handles bridge reads and writes

Each ReaderEntity exposes the entity handle. A plugin can therefore:

  1. Find an entity of interest with document_reader().for_each_entity(...).
  2. Use that handle with host.write_record(handle, record) to attach XDATA.
  3. Read the record back later with host.read_record(handle, app_name).

This is the primary read→write round-trip pattern the template demonstrates.

Ribbon integration

The plugin registers one ribbon module (SurveyTools) with one group (Survey). Each tool emits a ModuleEvent::Command(...) carrying the command name. The host routes the command to BuiltinPlugin::dispatch.


Building and running

From the workspace root:

cargo build -p plugin-template-v2

The resulting cdylib plus plugin.toml (checked in alongside this file) can be installed into the host's plugins folder (see docs/plugin-architecture.md). plugin.toml's rustc_version/acadrust_source placeholders are filled by CI in docs/plugin-template (v1) — this template has no release workflow of its own, so fill them by hand (rustc --version, and Cargo.lock's resolved acadrust package source line) before installing a real build.


Compatibility

The template is built against the current ocs_plugin_api and declares ApiVersion::CURRENT. Older plugins compiled against API v2 continue to load on an API v3 host because new HostApi methods are appended at the end of the trait and the host accepts plugin majors from API_VERSION_MIN_SUPPORTED up to API_VERSION.