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

83 lines
3.0 KiB
Markdown

# `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`](ocs_plugin_api::host::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:
```bash
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`.