Repository: camunda/camunda-modeler
Pull request #4906: feat: add resources integration
Author: login: barmac
Created: 2025-03-20T13:17:57Z | Merged: 2025-03-21T16:04:25Z

### Proposed Changes

https://github.com/user-attachments/assets/68ca0090-f8b5-4153-b3ae-d9cf7be473d8

This pull request adds the resources integration. In the context of a process application, you can directly create tasks with links to the processes, decisions, and forms available in the process application.

Closes #4668

### Checklist

To ensure you provided everything we need to look at your PR:

* [x] __Brief textual description__ of the changes present
* [x] __Visual demo__ attached
* [ ] __Steps to try out__ present, i.e. [using the `@bpmn-io/sr` tool](https://github.com/bpmn-io/sr)
* [x] Related issue linked via `Closes {LINK_TO_ISSUE}` or `Related to {LINK_TO_ISSUE}`

<!--
Thanks for creating this pull request! ❤️
-->


Linked issue #4668: Be able to link resources in process application
### Problem you would like to solve

In a process application users should be able to link BPMN processes, DMN diagrams and forms similar to the web modeler.

In the web modeler you can link by

* using the properties panel
  * resources that can be linked aren't shown (e.g. through autocomplete)
  * no validation of linked resource

![image](https://github.com/user-attachments/assets/b9c5015e-00ee-436d-817b-95dacf7d3824)

* using overlay
  * resources that can be linked are shown
  * validation of linked resource

![image](https://github.com/user-attachments/assets/951c4023-4061-48f6-a68c-78bea569bd06)


### Proposed solution

* [ ] resources can be linked to each other
* [ ] resource links will be linted
  * [ ] broken resource links will be shown in _Problems_ tab 
* [ ] to be decided
  * [ ] how can resources be linked
    * [ ] properties panel only (autocomplete could be added)
    * [ ] overlay

# User flow

1. Autocompletion in the ID input

![image](https://github.com/user-attachments/assets/abeaa6a6-e7df-46a8-a30b-2d58723bbcbf)

2. Creating elements with linked resources (e.g. create call activity with process linked already)

![image](https://github.com/user-attachments/assets/d672fc4b-2b03-44e7-859f-a0def232ed6b)

### Alternatives considered

None.

### Additional context

Related to https://github.com/camunda/camunda-modeler/issues/2508

_Product hub epic https://github.com/camunda/product-hub/issues/2608_
Linked issue comments: user: login: philippfromme
created_at: 2024-11-21T13:23:32Z
body: Related to https://github.com/bpmn-io/bpmn-js-properties-panel/issues/708
author_association: CONTRIBUTOR
user: login: barmac
created_at: 2025-03-11T16:17:12Z
body: Moved to ready and linked product hub epic.
author_association: COLLABORATOR
user: login: barmac
created_at: 2025-03-20T12:49:21Z
body: Solution sketch for resources integration:

![Image](https://github.com/user-attachments/assets/42814bb3-5357-4050-8c2e-c461ef662226)
author_association: COLLABORATOR
user: login: barmac
created_at: 2025-03-20T13:21:55Z
body: Draft PR: https://github.com/camunda/camunda-modeler/pull/4906
author_association: COLLABORATOR

Diff:
diff --git a/app/lib/file-context/__tests__/file-context-spec.js b/app/lib/file-context/__tests__/file-context-spec.js
index 6c3b2df602..7e12581355 100644
--- a/app/lib/file-context/__tests__/file-context-spec.js
+++ b/app/lib/file-context/__tests__/file-context-spec.js
@@ -96,7 +96,12 @@ describe('FileContext', function() {
     expect(item.metadata).to.exist;
     expect(item.metadata).eql({
       type: 'bpmn',
-      ids: [ 'FooProcess' ],
+      processes: [
+        {
+          id: 'FooProcess',
+          name: 'FooProcess'
+        }
+      ],
       linkedIds: [
         {
           type: 'bpmn',
@@ -386,7 +391,7 @@ describe('FileContext', function() {
       expect(item).to.exist;
       expect(item.metadata).to.eql({
         type: 'bpmn',
-        ids: [],
+        processes: [],
         linkedIds: []
       });
 
@@ -406,7 +411,7 @@ describe('FileContext', function() {
       expect(item).to.exist;
       expect(item.metadata).to.eql({
         type: 'dmn',
-        ids: [],
+        decisions: [],
         linkedIds: []
       });
 
@@ -426,7 +431,7 @@ describe('FileContext', function() {
       expect(item).to.exist;
       expect(item.metadata).to.eql({
         type: 'form',
-        ids: [],
+        forms: [],
         linkedIds: []
       });
 
@@ -501,8 +506,11 @@ describe('FileContext', function() {
       expect(item).to.exist;
 
       expect(item.metadata).to.eql({
-        'ids': [
-          'FooProcess'
+        'processes': [
+          {
+            'id': 'FooProcess',
+            'name': 'FooProcess'
+          }
         ],
         'linkedIds': [
           {
@@ -535,10 +543,19 @@ describe('FileContext', function() {
       expect(item).to.exist;
 
       expect(item.metadata).to.eql({
-        'ids': [
-          'BarDecision',
-          'BazDecision',
-          'FooDecision'
+        'decisions': [
+          {
+            'id': 'BarDecision',
+            'name': 'BarDecision'
+          },
+          {
+            'id': 'BazDecision',
+            'name': 'BazDecision'
+          },
+          {
+            'id': 'FooDecision',
+            'name': 'FooDecision'
+          },
         ],
         'linkedIds': [],
         'type': 'dmn'
@@ -565,8 +582,11 @@ describe('FileContext', function() {
       expect(item).to.exist;
 
       expect(item.metadata).to.eql({
-        'ids': [
-          'BazForm',
+        'forms': [
+          {
+            'id': 'BazForm',
+            'name': 'BazForm'
+          }
         ],
         'linkedIds': [],
         'type': 'form'
@@ -677,4 +697,4 @@ function readFile(filePath) {
 
 function writeFile(filePath, contents) {
   fs.writeFileSync(filePath, contents, 'utf8');
-}
\ No newline at end of file
+}
diff --git a/app/lib/file-context/__tests__/fixtures/extensions/.process-application b/app/lib/file-context/__tests__/fixtures/extensions/.process-application
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/app/lib/file-context/__tests__/fixtures/extensions/.process-application
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/app/lib/file-context/processors/bpmnProcessor.js b/app/lib/file-context/processors/bpmnProcessor.js
index 54782d89e5..5982766763 100644
--- a/app/lib/file-context/processors/bpmnProcessor.js
+++ b/app/lib/file-context/processors/bpmnProcessor.js
@@ -29,17 +29,17 @@ module.exports = {
     if (!item.file.contents) {
       return {
         type: 'bpmn',
-        ids: [],
+        processes: [],
         linkedIds: []
       };
     }
 
-    let rootElement, ids, linkedIds;
+    let rootElement, processes, linkedIds;
 
     try {
       ({ rootElement } = await moddle.fromXML(item.file.contents));
 
-      ids = findProcessIds(rootElement);
+      processes = findProcesses(rootElement);
       linkedIds = [
         ...findLinkedProcessIds(rootElement),
         ...findLinkedDecisionIds(rootElement),
@@ -51,24 +51,27 @@ module.exports = {
 
     return {
       type: 'bpmn',
-      ids,
+      processes,
       linkedIds
     };
   }
 };
 
-function findProcessIds(definitions) {
-  const processIds = [];
+function findProcesses(definitions) {
+  const processes = [];
 
   traverse(definitions, {
     enter(element) {
       if (is(element, 'bpmn:Process')) {
-        processIds.push(element.get('id'));
+        processes.push({
+          id: element.get('id'),
+          name: element.get('name') || element.get('id')
+        });
       }
     }
   });
 
-  return processIds;
+  return processes;
 }
 
 /**
@@ -121,4 +124,4 @@ function findLinkedDecisionIds(definitions) {
 
 function findLinkedFormIds(definitions) {
   return findLinkedIds(definitions, 'form', 'bpmn:UserTask', 'zeebe:FormDefinition', 'formId');
-}
\ No newline at end of file
+}
diff --git a/app/lib/file-context/processors/dmnProcessor.js b/app/lib/file-context/processors/dmnProcessor.js
index 6c9efdda91..72fa1d6b32 100644
--- a/app/lib/file-context/processors/dmnProcessor.js
+++ b/app/lib/file-context/processors/dmnProcessor.js
@@ -26,39 +26,42 @@ module.exports = {
     if (!item.file.contents) {
       return {
         type: 'dmn',
-        ids: [],
+        decisions: [],
         linkedIds: []
       };
     }
 
-    let rootElement, ids;
+    let rootElement, decisions;
 
     try {
       ({ rootElement } = await moddle.fromXML(item.file.contents));
 
-      ids = findDecisionIds(rootElement);
+      decisions = findDecisions(rootElement);
     } catch (error) {
       throw new Error(`Failed to parse DMN file: ${ error.message }`);
     };
 
     return {
       type: 'dmn',
-      ids,
+      decisions,
       linkedIds: []
     };
   }
 };
 
-function findDecisionIds(definitions) {
-  const decisionIds = [];
+function findDecisions(definitions) {
+  const decisions = [];
 
   traverse(definitions, {
     enter(element) {
       if (is(element, 'dmn:Decision')) {
-        decisionIds.push(element.get('id'));
+        decisions.push({
+          id: element.get('id'),
+          name: element.get('name') || element.get('id')
+        });
       }
     }
   });
 
-  return decisionIds;
-}
\ No newline at end of file
+  return decisions;
+}
diff --git a/app/lib/file-context/processors/formProcessor.js b/app/lib/file-context/processors/formProcessor.js
index 9c27b6983f..05a001b09a 100644
--- a/app/lib/file-context/processors/formProcessor.js
+++ b/app/lib/file-context/processors/formProcessor.js
@@ -19,25 +19,28 @@ module.exports = {
     if (!item.file.contents) {
       return {
         type: 'form',
-        ids: [],
+        forms: [],
         linkedIds: []
       };
     }
 
-    let formId;
+    let forms = [];
 
     try {
       const form = JSON.parse(item.file.contents);
-      formId = form.id;
+      forms.push({
+        id: form.id,
+        name: form.name || form.id
+      });
 
-      assert(formId, 'Form must have an id');
+      assert(form.id, 'Form must have an id');
     } catch (error) {
       throw new Error(`Failed to parse form file: ${ error.message }`);
     }
 
     return {
       type: 'form',
-      ids: [ formId ],
+      forms,
       linkedIds: []
     };
   }
diff --git a/client/src/app/tabs/cloud-bpmn/BpmnEditor.js b/client/src/app/tabs/cloud-bpmn/BpmnEditor.js
index 39066e6728..1427e47294 100644
--- a/client/src/app/tabs/cloud-bpmn/BpmnEditor.js
+++ b/client/src/app/tabs/cloud-bpmn/BpmnEditor.js
@@ -715,6 +715,10 @@ export class BpmnEditor extends CachedComponent {
       return this.loadTemplates();
     }
 
+    if (action === 'resources.reload') {
+      return modeler.get('resources.resourceLoader').reload();
+    }
+
     // TODO(nikku): handle all editor actions
     return modeler.get('editorActions').trigger(action, context);
   };
diff --git a/client/src/app/tabs/cloud-bpmn/__tests__/BpmnEditorSpec.js b/client/src/app/tabs/cloud-bpmn/__tests__/BpmnEditorSpec.js
index 36519618b3..dab94fd394 100644
--- a/client/src/app/tabs/cloud-bpmn/__tests__/BpmnEditorSpec.js
+++ b/client/src/app/tabs/cloud-bpmn/__tests__/BpmnEditorSpec.js
@@ -1702,6 +1702,38 @@ describe('cloud-bpmn - <BpmnEditor>', function() {
   });
 
 
+  describe('resources', function() {
+
+    it('should reload resources on action triggered', async function() {
+
+      // given
+      const resourceLoaderStub = sinon.stub({ reload() {} });
+
+      const cache = new Cache();
+
+      cache.add('editor', {
+        cached: {
+          modeler: new BpmnModeler({
+            modules: {
+              'resources.resourceLoader': resourceLoaderStub
+            }
+          })
+        }
+      });
+
+      // when
+      const { instance } = await renderEditor(diagramXML, {
+        cache
+      });
+
+      await instance.triggerAction('resources.reload');
+
+      // expect
+      expect(resourceLoaderStub.reload).to.be.calledOnce;
+    });
+  });
+
+
   describe('dirty state', function() {
 
     let instance;
diff --git a/client/src/plugins/process-applications/ProcessApplications.js b/client/src/plugins/process-applications/ProcessApplications.js
index d4d707e2c3..96920dfffe 100644
--- a/client/src/plugins/process-applications/ProcessApplications.js
+++ b/client/src/plugins/process-applications/ProcessApplications.js
@@ -130,6 +130,10 @@ export default class ProcessApplications {
     this._events.on(...args);
   }
 
+  off(...args) {
+    this._events.off(...args);
+  }
+
   /**
    * Check if item is process application item.
    *
@@ -166,4 +170,4 @@ export default class ProcessApplications {
   findItem(path) {
     return this._items.find(item => item.file.path === path);
   }
-}
\ No newline at end of file
+}
diff --git a/client/src/plugins/process-applications/ProcessApplicationsPlugin.js b/client/src/plugins/process-applications/ProcessApplicationsPlugin.js
index 88f2bd171d..d1b94f569c 100644
--- a/client/src/plugins/process-applications/ProcessApplicationsPlugin.js
+++ b/client/src/plugins/process-applications/ProcessApplicationsPlugin.js
@@ -12,6 +12,7 @@ import React, { useEffect, useState } from 'react';
 
 import ProcessApplications from './ProcessApplications';
 import ProcessApplicationsStatusBar from './ProcessApplicationsStatusBar';
+import { ResourcesProviderModule } from './ResourcesProvider';
 
 const processApplications = new ProcessApplications();
 
@@ -91,8 +92,41 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      const processApplicationsHelper = {
+        getItems() {
+          return processApplications.getItems();
+        }
+      };
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplicationsHelper ]
+            },
+            ResourcesProviderModule
+          ]
+        };
+      });
+
+    });
   }, []);
 
+  useEffect(() => {
+    if (activeTab?.type === 'cloud-bpmn') {
+      triggerAction('resources.reload');
+    }
+  }, [ activeTab, processApplicationItems ]);
+
   useEffect(() => {
     const tabGroups = tabs.reduce((tabGroups, tab) => {
       if (!tab.file) {
@@ -150,4 +184,4 @@ function createProcessApplicationFile(contents = {}) {
     contents: JSON.stringify(contents, null, 2),
     path: null
   };
-}
\ No newline at end of file
+}
diff --git a/client/src/plugins/process-applications/ResourcesProvider.js b/client/src/plugins/process-applications/ResourcesProvider.js
new file mode 100644
index 0000000000..2d28a462ca
--- /dev/null
+++ b/client/src/plugins/process-applications/ResourcesProvider.js
@@ -0,0 +1,82 @@
+/**
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership.
+ *
+ * Camunda licenses this file to you under the MIT; you may not use this file
+ * except in compliance with the MIT License.
+ */
+
+export class ResourcesProvider {
+  constructor(resourceLoader, processApplications) {
+    this._processApplications = processApplications;
+
+    resourceLoader.register(this);
+  }
+
+  getResources() {
+    const items = this._processApplications.getItems();
+
+    const resources = items.map((item) => {
+      switch (item.metadata.type) {
+      case 'bpmn':
+        return handleBpmnItem(item);
+      case 'dmn':
+        return handleDmnItem(item);
+      case 'form':
+        return handleFormItem(item);
+      default:
+        return null;
+      }
+    }).filter(Boolean).flat();
+
+    return resources;
+  }
+}
+
+ResourcesProvider.$inject = [ 'resources.resourceLoader', 'processApplications' ];
+
+export const ResourcesProviderModule = {
+  __init__: [ 'resourcesProvider' ],
+  resourcesProvider: [ 'type', ResourcesProvider ]
+};
+
+/**
+ * @param {{ metadata: { ids: string[] } }} item
+ */
+function handleBpmnItem(item) {
+  const metadata = item.metadata;
+
+  return metadata.processes.map((process) => ({
+    name: process.name,
+    type: 'bpmnProcess',
+    processId: process.id
+  }));
+}
+
+/**
+ * @param {{ metadata: { ids: string[] } }} item
+ */
+function handleDmnItem(item) {
+  const metadata = item.metadata;
+
+  return metadata.decisions.map((decision) => ({
+    name: decision.name,
+    type: 'dmnDecision',
+    decisionId: decision.id
+  }));
+}
+
+/**
+ * @param {{ metadata: { ids: string[] } }} item
+ */
+function handleFormItem(item) {
+  const metadata = item.metadata;
+
+  return metadata.forms.map((form) => ({
+    name: form.name,
+    type: 'form',
+    formId: form.id
+  }));
+}
diff --git a/client/src/plugins/process-applications/__tests__/ProcessApplicationsSpec.js b/client/src/plugins/process-applications/__tests__/ProcessApplicationsSpec.js
index 52dd7da1de..52adcda17b 100644
--- a/client/src/plugins/process-applications/__tests__/ProcessApplicationsSpec.js
+++ b/client/src/plugins/process-applications/__tests__/ProcessApplicationsSpec.js
@@ -197,6 +197,31 @@ describe('ProcessApplications', function() {
 
   });
 
+
+  describe('events', function() {
+
+    it('should allow to subscribe and unsubscribe', function() {
+
+      // given
+      const callback = spy();
+
+      // when
+      processApplications.on('event', callback);
+      processApplications.emit('event');
+
+      // then
+      expect(callback).to.have.been.calledOnce;
+
+      // and when
+      callback.resetHistory();
+      processApplications.off('event', callback);
+      processApplications.emit('event');
+
+      // then
+      expect(callback).not.to.have.been.called;
+    });
+  });
+
 });
 
 const DEFAULT_ITEMS = [
@@ -273,4 +298,4 @@ const UNSAVED_TAB = {
 
 const EMPTY_TAB = {
   file: null
-};
\ No newline at end of file
+};
diff --git a/client/src/plugins/process-applications/__tests__/ResourcesProviderSpec.js b/client/src/plugins/process-applications/__tests__/ResourcesProviderSpec.js
new file mode 100644
index 0000000000..dee89d021d
--- /dev/null
+++ b/client/src/plugins/process-applications/__tests__/ResourcesProviderSpec.js
@@ -0,0 +1,271 @@
+/**
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
+ * under one or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership.
+ *
+ * Camunda licenses this file to you under the MIT; you may not use this file
+ * except in compliance with the MIT License.
+ */
+
+import { ResourcesProvider } from '../ResourcesProvider';
+
+
+
+const DEFAULT_ITEMS = [
+  {
+    file: {
+      name: 'foo.bpmn',
+      uri: 'file:///C:/process-application/foo.bpmn',
+      path: 'C://process-application/foo.bpmn',
+      dirname: 'C://process-application',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      processes: [
+        {
+          id: 'Process_1',
+          name: 'Foo'
+        }
+      ],
+      type: 'bpmn'
+    }
+  },
+  {
+    file: {
+      name: 'bar.bpmn',
+      uri: 'file:///C:/bar.bpmn',
+      path: 'C://bar.bpmn',
+      dirname: 'C://',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      processes: [
+        {
+          id: 'Process_2',
+          name: 'Bar'
+        }
+      ],
+      type: 'bpmn'
+    }
+  }
+];
+
+const DEFAULT_ITEMS_PROCESS_APPLICATION = [
+  {
+    file: {
+      name: '.process-application',
+      uri: 'file:///C:/process-application/.process-application',
+      path: 'C://process-application/.process-application',
+      dirname: 'C://process-application',
+      contents: '{}'
+    },
+    metadata: {
+      type: 'processApplication'
+    }
+  }
+];
+
+const DEFAULT_ITEMS_DMN = [
+  {
+    file: {
+      name: 'foo.dmn',
+      uri: 'file:///C:/process-application/foo.dmn',
+      path: 'C://process-application/foo.dmn',
+      dirname: 'C://process-application',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      decisions: [
+        {
+          id: 'Decision_1',
+          name: 'Foo'
+        }
+      ],
+      type: 'dmn'
+    }
+  },
+  {
+    file: {
+      name: 'bar.dmn',
+      uri: 'file:///C:/bar.dmn',
+      path: 'C://bar.dmn',
+      dirname: 'C://',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      decisions: [
+        {
+          id: 'Decision_2',
+          name: 'Bar'
+        },
+        {
+          id: 'Decision_3',
+          name: 'Baz'
+        }
+      ],
+      type: 'dmn'
+    }
+  }
+];
+
+const DEFAULT_ITEMS_FORM = [
+  {
+    file: {
+      name: 'foo.form',
+      uri: 'file:///C:/process-application/foo.form',
+      path: 'C://process-application/foo.form',
+      dirname: 'C://process-application',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      forms: [
+        {
+          id: 'Form_1',
+          name: 'Foo'
+        }
+      ],
+      type: 'form'
+    }
+  },
+  {
+    file: {
+      name: 'bar.form',
+      uri: 'file:///C:/bar.form',
+      path: 'C://bar.form',
+      dirname: 'C://',
+      contents: '<?xml version="1.0" encoding="UTF-8"?>'
+    },
+    metadata: {
+      forms: [
+        {
+          id: 'Form_2',
+          name: 'Bar'
+        }
+      ],
+      type: 'form'
+    }
+  }
+];
+
+
+describe('ResourcesProvider', function() {
+
+  it('should handle bpmn item', function() {
+
+    // given
+    const resourceLoader = createResourceLoader();
+    const processApplications = createProcessApplications(DEFAULT_ITEMS);
+
+    const resourcesProvider = new ResourcesProvider(resourceLoader, processApplications);
+
+    // when
+    const resources = resourcesProvider.getResources();
+
+    // then
+    expect(resources).to.eql([
+      {
+        name: 'Foo',
+        type: 'bpmnProcess',
+        processId: 'Process_1'
+      },
+      {
+        name: 'Bar',
+        type: 'bpmnProcess',
+        processId: 'Process_2'
+      }
+    ]);
+  });
+
+
+  it('should handle dmn item', function() {
+
+    // given
+    const resourceLoader = createResourceLoader();
+    const processApplications = createProcessApplications(DEFAULT_ITEMS_DMN);
+
+    const resourcesProvider = new ResourcesProvider(resourceLoader, processApplications);
+
+    // when
+    const resources = resourcesProvider.getResources();
+
+    // then
+    expect(resources).to.eql([
+      {
+        name: 'Foo',
+        type: 'dmnDecision',
+        decisionId: 'Decision_1'
+      },
+      {
+        name: 'Bar',
+        type: 'dmnDecision',
+        decisionId: 'Decision_2'
+      },
+      {
+        name: 'Baz',
+        type: 'dmnDecision',
+        decisionId: 'Decision_3'
+      }
+    ]);
+  });
+
+
+  it('should handle form item', function() {
+
+    // given
+    const resourceLoader = createResourceLoader();
+    const processApplications = createProcessApplications(DEFAULT_ITEMS_FORM);
+
+    const resourcesProvider = new ResourcesProvider(resourceLoader, processApplications);
+
+    // when
+    const resources = resourcesProvider.getResources();
+
+    // then
+    expect(resources).to.eql([
+      {
+        name: 'Foo',
+        type: 'form',
+        formId: 'Form_1'
+      },
+      {
+        name: 'Bar',
+        type: 'form',
+        formId: 'Form_2'
+      }
+    ]);
+  });
+
+
+  it('should ignore process application', function() {
+
+    // given
+    const resourceLoader = createResourceLoader();
+    const processApplications = createProcessApplications(DEFAULT_ITEMS_PROCESS_APPLICATION);
+
+    const resourcesProvider = new ResourcesProvider(resourceLoader, processApplications);
+
+    // when
+    const resources = resourcesProvider.getResources();
+
+    // then
+    expect(resources).to.eql([]);
+  });
+});
+
+
+// helper /////////
+
+function createResourceLoader() {
+  return {
+    register() {}
+  };
+}
+
+function createProcessApplications(items) {
+  return {
+    getItems() {
+      return items || [];
+    }
+  };
+}


Reviews:
user: login: philippfromme
body: Good job! 🏅 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T15:38:13Z
user: login: barmac
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T15:40:51Z
user: login: barmac
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T15:42:04Z
user: login: barmac
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T15:44:29Z
user: login: barmac
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-21T15:55:33Z

Review comments:
diff_hunk: @@ -91,8 +92,35 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplications ]
path: client/src/plugins/process-applications/ProcessApplicationsPlugin.js
user: login: philippfromme
body: I'm still not a fan of this as `processApplications` is not a bpmn-js extension and for clarity I'd like to maintain a clear separation between bpmn-js and the surrounding application. We might want to refactor this later.
created_at: 2025-03-21T15:37:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,8 +92,35 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplications ]
path: client/src/plugins/process-applications/ProcessApplicationsPlugin.js
user: login: barmac
body: What is your suggestion?
created_at: 2025-03-21T15:40:50Z
author_association: COLLABORATOR
diff_hunk: @@ -91,8 +92,35 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplications ]
path: client/src/plugins/process-applications/ProcessApplicationsPlugin.js
user: login: barmac
body: Perhaps we could have just a wrapper around the dependency?
```suggestion
              processApplications: [ 'value', {
                getItems() {
                  return processApplications.getItems();
                }
              } ]
```
created_at: 2025-03-21T15:42:04Z
author_association: COLLABORATOR
diff_hunk: @@ -91,8 +92,35 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplications ]
path: client/src/plugins/process-applications/ProcessApplicationsPlugin.js
user: login: barmac
body: I'll edit this so that ResourcesProvider gets only what it needs. If you have another idea, let's reconsider.
created_at: 2025-03-21T15:44:29Z
author_association: COLLABORATOR
diff_hunk: @@ -91,8 +92,35 @@ export default function ProcessApplicationsPlugin(props) {
         setProcessApplicationItems([]);
       }
     });
+
+    subscribe('bpmn.modeler.configure', ({ middlewares, tab }) => {
+
+      if (tab.type !== 'cloud-bpmn') {
+        return;
+      }
+
+      middlewares.push(config => {
+        return {
+          ...config,
+          additionalModules: [
+            ...config.additionalModules || [],
+            {
+              processApplications: [ 'value', processApplications ]
path: client/src/plugins/process-applications/ProcessApplicationsPlugin.js
user: login: barmac
body: Now on the branch.
created_at: 2025-03-21T15:55:33Z
author_association: COLLABORATOR

PR comments:
user: login: barmac
created_at: 2025-03-20T13:19:27Z
body: Known limitations:
* We use `id` as the label because the process applications feature does not provide names. This should be fixed for the release.
* All improvement ideas from Web Modeler PR: https://github.com/camunda/web-modeler/pull/13682#issuecomment-2737131099
author_association: COLLABORATOR
user: login: barmac
created_at: 2025-03-20T13:19:42Z
body: Check this out via `npm ci && npm start`.
author_association: COLLABORATOR
user: login: barmac
created_at: 2025-03-21T13:52:54Z
body: > We use id as the label because the process applications feature does not provide names. This should be fixed for the release.

This is implemented but forms lack name in the schema, so we have to use ID.
author_association: COLLABORATOR
user: login: barmac
created_at: 2025-03-21T14:22:14Z
body: > > We use id as the label because the process applications feature does not provide names. This should be fixed for the release.
> 
> This is implemented but forms lack name in the schema, so we have to use ID.

I've created an issue to track this: https://github.com/bpmn-io/form-js/issues/1376
author_association: COLLABORATOR