Skip to content

feat(studio): add host.apiBaseUrl and keep axios off the plugin surface - #1270

Merged
marcusds merged 3 commits into
mainfrom
studio-plugin-surface/mschwab
Aug 12, 2026
Merged

feat(studio): add host.apiBaseUrl and keep axios off the plugin surface#1270
marcusds merged 3 commits into
mainfrom
studio-plugin-surface/mschwab

Conversation

@marcusds

@marcusds marcusds commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Two fixes to the Studio plugin system, both found while building the first plugin that calls its own backend service rather than only platform services.

A plugin shipping its own API client previously had no supported way to reach the platform, and two entries on the @nemo/common plugin surface silently broke every plugin's bundle.

Related Issue

Changes

host.apiBaseUrl — new field on the plugin host handle.

host.sdk covers platform services only. A plugin that ships its own generated client has to build its own URLs, and a relative /apis/... request does not work: Studio's dev-server /apis proxy is opt-in (gated on VITE_PLATFORM_PROXY_DOMAIN and an empty base URL, web/packages/studio/vite.config.ts:501), so whenever VITE_PLATFORM_BASE_URL is set the request hits the Vite dev server instead of the platform. There was no workaround short of reaching into Studio's internals, which the plugin contract forbids.

PluginRenderer populates it from PLATFORM_BASE_URL, defaulting to '' for the same-origin case.

Keeping axios off the @nemo/common plugin surface.

Everything exported from web/packages/common/src/plugin.ts is bundled into public/vendor/common.js, which every plugin resolves through Studio's import map. Two components reach axios — ErrorPanel via api/common/utils, CancelJobButton via @nemo/sdk/generated/platform/api — and in the vendor build axios resolves to its Node build, so common.js emits bare imports of crypto, http, https, url, events, stream and zlib.

The browser cannot resolve those, so the dynamic import() of any plugin throws:

[plugins] Failed to load plugin "…": TypeError: Failed to resolve module specifier "crypto".

loadPlugin catches that, logs logger.warn and returns null (web/packages/studio/src/plugins/utils.ts), so the only user-visible symptom is a plugin quietly not appearing — no error surface at all. Because common.js is shared, one bad export breaks every installed plugin, not just the one using it.

Neither is exported. Both now carry a comment explaining why, since nothing about the failure points back to the cause.

Surface additionsControlledTextArea, ENTITY_NAME_HELP, entityNameSchema. All verified free of any transitive reach to @nemo/sdk's axios-bearing client.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification:

PluginRenderer.test.tsx asserts host.apiBaseUrl is a string — the case that matters is PLATFORM_BASE_URL being unset, where every plugin request would otherwise be prefixed with the literal "undefined". plugins/example-plugin/web/AGENTS.md is the canonical template plugins copy, so it carries the contract change and the rule about prefixing requests.

The axios guard is enforced by comment rather than by a test. A lint rule or a build assertion that fails when vendor/common.js emits an unmapped bare specifier would be the durable fix; I did not add one here to keep this change small, and it is worth doing separately.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

Command Result
pnpm --filter nemo-studio-ui typecheck 0 errors
pnpm --filter @nemo/common typecheck 0 errors
pnpm --filter nemo-studio-ui test src/plugins 27 passed
eslint packages/studio/src/plugins packages/common/src/plugin.ts clean
tsc --noEmit in plugins/example-plugin/web 0 errors
uv run pre-commit run -a 2 failures, both environmental — see below

pre-commit run -a is not fully green on my machine:

  • helm-docs — the binary is not installed locally. Touches no file in this change.
  • uv-lock — the hook requires uv 0.9.14; this machine has 0.9.30. The separate Check for uv.lock drift hook passes, so the lockfile is in sync; only the version guard fails. uv.lock is untouched by this PR.

Every other hook passes, including ruff, ty, copyright headers, Run UI lint-staged, and Plugins must not import from nmp-common.

Manually verified against a running platform that vendor/common.js and the other vendor bundles now emit only import-map specifiers, with no unmapped bare imports.

Summary by CodeRabbit

  • New Features
    • Plugins can access the platform API base URL through their host configuration.
    • Added plugin API exports for controlled text input and entity-name validation utilities.
  • Documentation
    • Documented how plugins should use the platform API base URL when calling services.
    • Clarified which components are intentionally excluded from the plugin API.
  • Tests
    • Added coverage confirming the API base URL is provided, including same-origin deployments.

…t axios

Two fixes to the Studio plugin system, both found while building the first
plugin that calls its own service.

host.apiBaseUrl
  A plugin that ships its own client had no way to reach the platform. Studio's
  dev-server `/apis` proxy is opt-in (gated on VITE_PLATFORM_PROXY_DOMAIN and an
  empty base URL), so a relative `/apis/...` request hits the dev server rather
  than the platform whenever VITE_PLATFORM_BASE_URL is set. host.sdk covers
  platform services only, so there was no workaround short of reaching into
  Studio's internals.

@nemo/common plugin surface
  Anything exported from plugin.ts is bundled into vendor/common.js, which every
  plugin loads through the import map. Two components reach axios — ErrorPanel
  via api/common/utils, CancelJobButton via @nemo/sdk/generated/platform/api —
  and axios resolves to its Node build there, emitting bare imports of crypto,
  http, https, url, events, stream and zlib. The browser cannot resolve those, so
  the dynamic import of *every* plugin throws, and loadPlugin swallows it with a
  logger.warn: the only symptom is a plugin silently not appearing.

  Neither is exported, and both now carry a comment saying why, since the
  failure mode gives no hint about the cause.

Also adds ControlledTextArea, ENTITY_NAME_HELP and entityNameSchema to the
surface — all free of SDK and axios reach.

Signed-off-by: mschwab <mschwab@nvidia.com>
The renderer builds it from PLATFORM_BASE_URL, which is optional. Asserting the
type catches the undefined case, where every plugin request would be prefixed
with the string "undefined".

Signed-off-by: mschwab <mschwab@nvidia.com>
@marcusds
marcusds requested review from a team as code owners August 12, 2026 23:05
@github-actions github-actions Bot added the feat label Aug 12, 2026
@marcusds
marcusds enabled auto-merge August 12, 2026 23:05
Signed-off-by: mschwab <mschwab@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 63ce5620-3241-426f-b3f7-36ef3ef010b5

📥 Commits

Reviewing files that changed from the base of the PR and between e354df1 and dd77167.

📒 Files selected for processing (1)
  • web/packages/common/src/plugin.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/packages/common/src/plugin.ts

📝 Walkthrough

Walkthrough

Changes

Plugin API updates

Layer / File(s) Summary
Browser-safe plugin exports
web/packages/common/src/plugin.ts
The plugin entrypoint documents excluded components and exports ControlledTextArea, ENTITY_NAME_HELP, and entityNameSchema.
Plugin host API base URL contract
web/packages/studio/src/plugins/types.ts, plugins/example-plugin/web/src/types.ts, plugins/example-plugin/web/AGENTS.md
Plugin host types and example-plugin guidance define the required apiBaseUrl property and its service-request behavior.
Plugin renderer host wiring
web/packages/studio/src/plugins/PluginRenderer.tsx, web/packages/studio/src/plugins/PluginRenderer.test.tsx
PluginRenderer supplies PLATFORM_BASE_URL or an empty string as apiBaseUrl. The test verifies that the value is a string.

Possibly related PRs

Mergeability Score: ⚪ Minimal · up to dd771

The PR adds a supported platform base URL for plugins and removes axios-bearing exports that could prevent browser plugin loading. Targeted checks pass, and no actionable merge-blocking risk remains after normal review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two primary changes: adding host.apiBaseUrl and removing axios from the plugin surface.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch studio-plugin-surface/mschwab

Comment @coderabbitai help to get the list of available commands.

@marcusds
marcusds added this pull request to the merge queue Aug 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 32235/40906 78.8% 63.7%
Integration Tests 18639/38832 48.0% 20.7%

Merged via the queue into main with commit b499ef8 Aug 12, 2026
57 checks passed
@marcusds
marcusds deleted the studio-plugin-surface/mschwab branch August 12, 2026 23:45
marcusds added a commit that referenced this pull request Aug 13, 2026
The host.apiBaseUrl paragraph landed in #1270 without a worked example, because
the plugin it describes was not on main yet.

Signed-off-by: mschwab <mschwab@nvidia.com>
marcusds added a commit that referenced this pull request Aug 13, 2026
The host.apiBaseUrl paragraph landed in #1270 without a worked example, because
the plugin it describes was not on main yet.

Signed-off-by: mschwab <mschwab@nvidia.com>
koralchapnik pushed a commit that referenced this pull request Aug 13, 2026
The host.apiBaseUrl paragraph landed in #1270 without a worked example, because
the plugin it describes was not on main yet.

Signed-off-by: mschwab <mschwab@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants