Skip to content

UN-4187 [MISC] Standardise optional plugin loading with a single loadPlugin helper - #2307

Open
jaseemjaskp wants to merge 1 commit into
mainfrom
UN-4187-standardise-plugin-loading
Open

jaseemjaskp wants to merge 1 commit into
mainfrom
UN-4187-standardise-plugin-loading

Conversation

@jaseemjaskp

@jaseemjaskp jaseemjaskp commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

What

  • Add loadPlugin(importer, fallback = null) to frontend/src/helpers/pluginLoader.js, with unit tests.
  • Migrate every optional-plugin import in the OSS frontend (42 files) from try { await import("../plugins/…") } catch {} to loadPlugin, one plugin module per call.
  • Replace isModuleMissing with isPluginAbsent, shared by loadPlugin and lazyPlugin.

Why

  • About 95 sites bypassed the central loader, and most had a bare catch. A real chunk-load or runtime error in a shipped plugin looked exactly like "plugin not installed" and vanished silently.
  • Fallbacks differed from site to site (null, undefined, no-ops, stores left undefined).
  • Several sites shared one try block, so one missing plugin disabled the others (e.g. ToolIde, CombinedOutput, SettingsModal).

How

  • loadPlugin returns the fallback when the plugin is absent (silent), when the import fails for any other reason (logged once as [plugin] failed to load), or when the picked export is missing.
  • isPluginAbsent matches only the build stub's Optional plugin not available plus the Node equivalents. It deliberately does not match Failed to fetch dynamically imported module: that is a shipped plugin whose chunk failed, so it is logged rather than treated as absent. The fallback is returned either way; only the logging differs.
  • Where code relied on the old all-or-nothing loading, a guard keeps the behaviour: the Summarize tab now requires its view, and PersistentLogin calls setSelectedProduct?.().
  • Router.jsx, useMainAppRoutes.jsx and PageLayout.jsx drop their hand-rolled isModuleMissing + console.error blocks; lazyPlugin route wrappers are unchanged.

Can this PR break any existing features? If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • Low risk. Each site loads the same modules and falls back to the same kind of value. What changes:
    • Plugins that shared a try block now load independently, so a partial failure disables less than before.
    • Load failures that were swallowed are now logged. Known new console error in cloud: useSessionValid.js has called the usePlatformAdmin hook at module level since Feat/friction less onboarding and usage reporting #256, which always throws Invalid hook call, so isPlatformAdmin has never been set. That pre-existing bug is now visible on every page load; fixing it changes access to RequirePlatformAdmin, so it is left for a separate ticket.
    • Values for absent plugins are null rather than undefined; no call site compares strictly against undefined.

Relevant Docs

  • The "Plugins" section added to prompting/FRONTEND_DEV_GUIDE.md in the companion cloud PR.

Related Issues or PRs

  • UN-4187 (epic UN-4182).
  • Companion cloud PR Zipstack/unstract-cloud#1806 migrates the cloud plugin sites and documents the pattern. Merge this PR first: the cloud plugins import the new helper.

Dependencies Versions / Env Variables

  • None.

Notes on Testing

  • npx vitest run: 759/761 pass. The 2 failures are in cascade-and-affordances.test.jsx and flag CSS/defaultProps in cloud plugin files copied into the checkout; none are touched here.
  • Biome: no errors. vite build succeeds with the cloud plugins and as an OSS-only build with src/plugins removed.
  • No bare catch remains around a plugin import.
  • Browser, cloud dev namespace (DevSpace HMR): LLMWhisperer playground, product switcher, dashboard (trial badge, Agentic Prompt Studio / HITL nav), Prompt Studio list (Look-Ups tab), a Prompt Studio project (SinglePass, Summary View, lookup indicators, Raw/Enriched toggle, settings modal with all three formerly-shared plugins), platform settings (enterprise-only control) and metrics dashboard all render.
  • Deliberately broken plugin (top-level throw in TrialDaysInfo.jsx): console shows [plugin] failed to load … deliberate broken plugin pointing at TopNavBar.jsx; the page still renders without the badge.
  • OSS-only build served locally boots to the OSS login page with no plugin messages.

Screenshots

...

Checklist

I have read and understood the Contribution Guidelines.

…helper

Every non-route plugin import used a module-level
`try { await import("../plugins/...") } catch {}`, most with a bare catch,
so a real chunk-load or runtime error was indistinguishable from "plugin
not installed". Fallbacks differed per site, and several sites shared one
try block so a single missing plugin disabled the others.

Add loadPlugin(importer, fallback) to helpers/pluginLoader.js: it returns
the fallback silently when the plugin is absent and logs every other
failure once. Absence is now isPluginAbsent, which is narrower than the
old isModuleMissing: a chunk that failed to fetch belongs to a plugin
that IS shipped, so it is logged instead of mistaken for absence.
pluginRegistry shares the same classifier.

Migrate every plugin import site to one loadPlugin call per plugin
module. Two call sites get a guard where code relied on the old
all-or-nothing loading: the Summarize tab now requires its view, and
PersistentLogin calls setSelectedProduct optionally.
@github-actions

Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

✅ All checks passed! No linting or formatting issues found.

@sonarqubecloud

Copy link
Copy Markdown

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

via Greptile

RetriggerConfidence Score: 4/5

[Medium risk] Refactors optional plugin loading across 43 frontend files.

The PR appears safe to merge, with non-blocking improvements needed to plugin-load diagnostics and lazy-route error classification.

Fix All in Claude CodeFindings

  1. P2 Missing exports go unnoticed ▶
  2. P2 Route errors become NotFound ▶
Fix with agent prompt
### Issue 1
frontend/src/helpers/pluginLoader.js:49
If a shipped plugin loads but its requested export was renamed or removed, the importer returns `undefined` and `loadPlugin` silently uses the fallback. The component or hook disappears without an error explaining why, making a broken plugin harder to diagnose. `lazyPlugin` already reports missing exports.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
frontend/src/helpers/pluginRegistry.js:4
If a shipped route plugin throws an error containing “Cannot find module” or carrying `MODULE_NOT_FOUND` while loading, the shared classifier now treats it as absent. `lazyPlugin` then renders NotFound instead of surfacing the plugin failure, making the broken route look like a missing route. The previous route-specific check matched only the optional-plugin stub error.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

This PR centralizes optional frontend plugin imports in loadPlugin, migrates component and hook call sites, and shares absence classification with lazy routes.

  • Independent loads prevent one unavailable plugin from disabling unrelated plugins in the same former try block.
  • Missing picked exports remain silent, while the shared classifier broadens which lazy-route errors render NotFound.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Optional plugin import] --> B{Consumer}
  B -->|Component, hook, or helper| C[loadPlugin]
  B -->|Lazy route| D[lazyPlugin]
  C --> E{Import result}
  E -->|Value| F[Use value]
  E -->|Missing export or error| G[Use fallback]
  D --> H{Import error classified absent?}
  H -->|Yes| I[Render NotFound]
  H -->|No| J[Surface route error]
Loading

Reviews (1) · Last reviewed commit: "UN-4187 Standardise optional plugin load..."

// plugins lets one missing plugin disable the others.
export async function loadPlugin(importer, fallback = null) {
try {
return (await importer()) ?? fallback;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing exports go unnoticed If a shipped plugin loads but its requested export was renamed or removed, the importer returns undefined and loadPlugin silently uses the fallback. The component or hook disappears without an error explaining why, making a broken plugin harder to diagnose. lazyPlugin already reports missing exports.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/helpers/pluginLoader.js
Line: 49

Comment:
**Missing exports go unnoticed** If a shipped plugin loads but its requested export was renamed or removed, the importer returns `undefined` and `loadPlugin` silently uses the fallback. The component or hook disappears without an error explaining why, making a broken plugin harder to diagnose. `lazyPlugin` already reports missing exports.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

function isPluginAbsent(err) {
return (err?.message || "").includes("Optional plugin not available");
}
import { isPluginAbsent } from "./pluginLoader.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Route errors become NotFound If a shipped route plugin throws an error containing “Cannot find module” or carrying MODULE_NOT_FOUND while loading, the shared classifier now treats it as absent. lazyPlugin then renders NotFound instead of surfacing the plugin failure, making the broken route look like a missing route. The previous route-specific check matched only the optional-plugin stub error.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/helpers/pluginRegistry.js
Line: 4

Comment:
**Route errors become NotFound** If a shipped route plugin throws an error containing “Cannot find module” or carrying `MODULE_NOT_FOUND` while loading, the shared classifier now treats it as absent. `lazyPlugin` then renders NotFound instead of surfacing the plugin failure, making the broken route look like a missing route. The previous route-specific check matched only the optional-plugin stub error.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
✅ e2e-api-deployment e2e 3 0 0 0 14.3
✅ e2e-coowners e2e 1 0 0 0 1.6
✅ e2e-etl e2e 1 0 0 0 8.4
✅ e2e-login e2e 2 0 0 0 1.2
✅ e2e-prompt-studio e2e 1 0 0 0 11.6
✅ e2e-smoke e2e 2 0 0 0 1.4
✅ e2e-workflow e2e 1 0 0 0 12.5
❌ ui e2e 0 1 0 0 0.0
TOTAL 11 1 0 0 50.9

Critical paths

⚠️ Critical paths not yet covered

  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
💤 Covered, but not exercised in this build
  • adapter-register-llm — Register and validate an LLM adapter. (covered by integration-backend; no result reported in this build)
  • workflow-author — Create a workflow; its source+destination endpoints materialise and are configurable. (covered by integration-backend; no result reported in this build)
  • api-deployment-provision — Deploying a workflow as an API mints a usable key and a resolvable endpoint. (covered by integration-backend; no result reported in this build)
  • api-deployment-auth — Unauthenticated or mis-scoped API-deployment calls are rejected before dispatch. (covered by integration-backend; no result reported in this build)
  • mcp-server-auth — Unauthenticated or mis-scoped hosted-MCP calls are rejected before any tool runs. (covered by integration-backend; no result reported in this build)
  • mcp-platform-auth — The org-scoped MCP endpoint stays behind the platform-API-key middleware; unauthenticated or mis-scoped calls reach no tool. (covered by integration-backend; no result reported in this build)
  • platform-key-whoami — A platform API key resolves its own organisation over the org-less whoami endpoint; the org comes from the key row, not the URL. (covered by integration-backend; no result reported in this build)
  • prompt-studio-author — Create a Prompt Studio project and add a prompt to it. (covered by integration-backend; no result reported in this build)
  • connector-register-test — Connector credentials are validated against the live system and stored encrypted. (covered by integration-backend; no result reported in this build)
  • usage-aggregate-read — Per-run token usage aggregates correctly and stays scoped to its organization. (covered by integration-backend; no result reported in this build)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • co-owner-manage — covered by e2e-coowners
  • workflow-create-execute — covered by e2e-workflow
  • api-deployment-run — covered by e2e-api-deployment
  • prompt-studio-fetch-response — covered by e2e-prompt-studio
  • pipeline-etl-execute — covered by e2e-etl
  • usage-token-tracking — covered by e2e-api-deployment
  • callback-result-delivery — covered by e2e-api-deployment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant