diff --git a/projects/scion/workbench-application-platform.api/src/lib/core.model.ts b/projects/scion/workbench-application-platform.api/src/lib/core.model.ts
index 8ebc037bd..82d1d16dd 100644
--- a/projects/scion/workbench-application-platform.api/src/lib/core.model.ts
+++ b/projects/scion/workbench-application-platform.api/src/lib/core.model.ts
@@ -139,13 +139,6 @@ export interface Capability {
      * Symbolic name of the application which provides this capability.
      */
     symbolicAppName: string;
-    /**
-     * Indicates if the capability implementor acts as a proxy through which intents are processed.
-     *
-     * For example, `ViewIntentHandler` is a proxy for application view capabilities which
-     * reads config from registered view capability providers and dispatches intents to the Angular router.
-     */
-    proxy: boolean;
   };
 }
 
diff --git a/projects/scion/workbench-application-platform/src/lib/activity-capability/activity-registrator.service.ts b/projects/scion/workbench-application-platform/src/lib/activity-capability/activity-registrator.service.ts
index b7e716b5b..671d1fdb0 100644
--- a/projects/scion/workbench-application-platform/src/lib/activity-capability/activity-registrator.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/activity-capability/activity-registrator.service.ts
@@ -34,8 +34,7 @@ export class ActivityRegistrator {
    */
   public init(): void {
     this._manifestCollector.whenManifests.then(manifestRegistry => {
-      const activityCapabilities: ActivityCapability[] = manifestRegistry.getCapabilitiesByType<ActivityCapability>(PlatformCapabilityTypes.Activity)
-        .filter(capability => !capability.metadata.proxy);
+      const activityCapabilities: ActivityCapability[] = manifestRegistry.getCapabilitiesByType<ActivityCapability>(PlatformCapabilityTypes.Activity);
       this.installActivityCapabilityRoutes(activityCapabilities);
       this.registerActivities(activityCapabilities);
     });
@@ -46,7 +45,6 @@ export class ActivityRegistrator {
     this._routesRegistrator.replaceRouterConfig([
       ...this._router.config,
       ...activityCapabilities
-        .filter(activityCapability => !activityCapability.metadata.proxy)
         .map((activityCapability: ActivityCapability): Route => {
           return {
             path: `${activityCapability.metadata.symbolicAppName}/${activityCapability.metadata.id}`,
@@ -62,7 +60,6 @@ export class ActivityRegistrator {
 
   private registerActivities(activityCapabilities: ActivityCapability[]): void {
     activityCapabilities
-      .filter(activityCapability => !activityCapability.metadata.proxy)
       .forEach(activityCapability => {
         const activity = this._activityPartService.createActivity();
         activity.title = activityCapability.properties.title;
diff --git a/projects/scion/workbench-application-platform/src/lib/core/array.util.ts b/projects/scion/workbench-application-platform/src/lib/core/array.util.ts
index 917fd72f8..0d9871420 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/array.util.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/array.util.ts
@@ -8,8 +8,10 @@
  *  SPDX-License-Identifier: EPL-2.0
  */
 
+import { QueryList } from '@angular/core';
+
 /**
- * Provides array utlity methods.
+ * Provides array utility methods.
  */
 export class Arrays {
 
@@ -25,4 +27,54 @@ export class Arrays {
     }
     return Array.isArray(value) ? value : [value];
   }
+
+  /**
+   * Compares items of given arrays for reference equality.
+   *
+   * Use the parameter `exactOrder` to control if the item order must be equal.
+   */
+  public static equal(array1: any[], array2: any[], exactOrder: boolean = true): boolean {
+    if (array1 === array2) {
+      return true;
+    }
+
+    if (array1.length !== array2.length) {
+      return false;
+    }
+
+    return array1.every((item, index) => {
+      if (exactOrder) {
+        return item === array2[index];
+      }
+      else {
+        return array2.includes(item);
+      }
+    });
+  }
+
+  /**
+   * Finds the last item matching the given predicate, if any,
+   * or returns the last item in the array if no predicate is specified.
+   *
+   * Returns `undefined` if no element is found.
+   */
+  public static last<T>(items: T[] | QueryList<T>, predicate?: (item: T) => boolean): T | undefined {
+    const array = items ? (Array.isArray(items) ? items : items.toArray()) : [];
+
+    if (!predicate) {
+      return array[array.length - 1];
+    }
+    return [...array].reverse().find(predicate);
+  }
+
+  /**
+   * Removes given item from the array. The original array will not be modified.
+   */
+  public static remove<T>(items: T[], item: T): T[] {
+    const result = [...items];
+    for (let index = result.indexOf(item); index !== -1; index = result.indexOf(item)) {
+      result.splice(index, 1);
+    }
+    return result;
+  }
 }
diff --git a/projects/scion/workbench-application-platform/src/lib/core/intent-handler-registrator.service.ts b/projects/scion/workbench-application-platform/src/lib/core/intent-handler-registrator.service.ts
index 928905dd1..0a7e289ad 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/intent-handler-registrator.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/intent-handler-registrator.service.ts
@@ -15,9 +15,8 @@ import { Subject } from 'rxjs';
 import { ManifestRegistry } from './manifest-registry.service';
 import { filter, takeUntil } from 'rxjs/operators';
 import { MessageBus } from './message-bus.service';
-import { ApplicationRegistry } from './application-registry.service';
 import { MessageEnvelope, NilQualifier } from '@scion/workbench-application-platform.api';
-import { testQualifier } from './qualifier-tester';
+import { matchesIntentQualifier } from './qualifier-tester';
 
 /**
  * Registers intent handlers registered via {INTENT_HANDLER} DI injection token.
@@ -30,7 +29,6 @@ export class IntentHandlerRegistrator implements OnDestroy {
   private _destroy$ = new Subject<void>();
 
   constructor(@Inject(INTENT_HANDLER) private _handlers: IntentHandler[],
-              private _applicationRegistry: ApplicationRegistry,
               private _manifestRegistry: ManifestRegistry,
               private _messageBus: MessageBus,
               private _logger: Logger) {
@@ -50,14 +48,14 @@ export class IntentHandlerRegistrator implements OnDestroy {
       qualifier: handler.qualifier || NilQualifier,
       private: false,
       description: handler.description,
-    }], handler.proxy);
+    }]);
 
-    handler.onInit && handler.onInit(this._applicationRegistry, this._manifestRegistry);
+    handler.onInit && handler.onInit();
 
     this._messageBus.receiveIntentsForApplication$(HOST_APPLICATION_SYMBOLIC_NAME)
       .pipe(
         filter(envelope => envelope.message.type === handler.type),
-        filter(envelope => testQualifier(handler.qualifier, envelope.message.qualifier)),
+        filter(envelope => matchesIntentQualifier(handler.qualifier, envelope.message.qualifier)),
         takeUntil(this._destroy$),
       )
       .subscribe((envelope: MessageEnvelope) => {
diff --git a/projects/scion/workbench-application-platform/src/lib/core/manifest-registry.service.ts b/projects/scion/workbench-application-platform/src/lib/core/manifest-registry.service.ts
index 08649e5a0..4aff1a8be 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/manifest-registry.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/manifest-registry.service.ts
@@ -12,9 +12,7 @@ import { Injectable } from '@angular/core';
 import { Capability, Intent, Qualifier } from '@scion/workbench-application-platform.api';
 import { Defined } from './defined.util';
 import { sha256 } from 'js-sha256';
-import { testQualifier } from './qualifier-tester';
-
-const NilSelector = (capability: Capability): boolean => true;
+import { matchesCapabilityQualifier, matchesIntentQualifier } from './qualifier-tester';
 
 /**
  * Registry with all registered application capabilities and intents.
@@ -53,7 +51,22 @@ export class ManifestRegistry {
    * Returns capabilities which have the given required type and qualifiers.
    */
   public getCapabilities<T extends Capability>(type: string, qualifier: Qualifier): T[] {
-    return this.getCapabilitiesByType(type).filter(capability => testQualifier(capability.qualifier, qualifier)) as T[];
+    return this.getCapabilitiesByType(type)
+      .filter(capability => matchesCapabilityQualifier(capability.qualifier, qualifier)) as T[];
+  }
+
+  /**
+   * Returns capabilities which match the given predicate.
+   */
+  public getCapabilitiesByPredicate(predicate: (capability: Capability) => boolean): Capability[] {
+    return Array.from(this._capabilitiesById.values()).filter(predicate);
+  }
+
+  /**
+   * Checks if the given capability is visible to the given application.
+   */
+  public isVisibleForApplication(capability: Capability, symbolicName: string): boolean {
+    return !capability.private || this.isScopeCheckDisabled(symbolicName) || capability.metadata.symbolicAppName === symbolicName;
   }
 
   /**
@@ -71,22 +84,21 @@ export class ManifestRegistry {
   }
 
   /**
-   * Tests if the application has registered an intent for the given type and qualifiers,
+   * Tests if the specified application has registered an intent for the given type and qualifiers,
    * or whether the application has an implicit intent because it provides the capability itself.
    */
   public hasIntent(symbolicName: string, type: string, qualifier: Qualifier): boolean {
     return this.getIntentsByApplication(symbolicName).some(intent => {
-      return intent.type === type && testQualifier(intent.qualifier, qualifier);
+      return intent.type === type && matchesIntentQualifier(intent.qualifier, qualifier);
     });
   }
 
   /**
-   * Tests if the application has registered a capability for the given type and qualifiers,
-   * and if specified, which also matches the selector function.
+   * Tests if the specified application has registered a capability for the given type and qualifiers.
    */
-  public hasCapability(symbolicName: string, type: string, qualifier: Qualifier, selector: (capability: Capability) => boolean = NilSelector): boolean {
-    const capabilities = this.getCapabilities(type, qualifier);
-    return capabilities.some(capability => capability.metadata.symbolicAppName === symbolicName && selector(capability));
+  public hasCapability(symbolicName: string, type: string, qualifier: Qualifier): boolean {
+    return this.getCapabilities(type, qualifier)
+      .some(capability => capability.metadata.symbolicAppName === symbolicName);
   }
 
   /**
@@ -94,22 +106,22 @@ export class ManifestRegistry {
    * The capability must be provided with public visibility unless provided by the requesting application itself.
    */
   public isHandled(symbolicName: string, type: string, qualifier: Qualifier): boolean {
-    return this.getCapabilities(type, qualifier)
-      .filter(capability => !capability.metadata.proxy)
-      .some(capability => !capability.private || this.isScopeCheckDisabled(symbolicName) || capability.metadata.symbolicAppName === symbolicName);
+    return this.getCapabilities(type, qualifier).some(capability => this.isVisibleForApplication(capability, symbolicName));
   }
 
   /**
    * Registers capabilities of the given application.
-   *
-   * The parameter 'proxy' indicates the implementor act as a proxy through which intents are processed (which by default is false).
    */
-  public registerCapability(symbolicName: string, capabilities: Capability[], proxy: boolean = false): void {
+  public registerCapability(symbolicName: string, capabilities: Capability[]): void {
     if (!capabilities || !capabilities.length) {
       return;
     }
 
     capabilities.forEach(it => {
+      if (it.hasOwnProperty('*')) {
+        throw Error(`[CapabilityRegistrationError] Capability qualifiers do not support \`*\` as key`);
+      }
+
       const registeredCapabilities = this._capabilitiesByType.get(it.type) || [];
       const capability: Capability = {
         ...it,
@@ -117,7 +129,6 @@ export class ManifestRegistry {
         metadata: {
           id: sha256(JSON.stringify({application: symbolicName, type: it.type, ...it.qualifier})).substr(0, 7), // use the first 7 digits of the capability hash as capability id
           symbolicAppName: symbolicName,
-          proxy: proxy,
         },
       };
 
@@ -126,9 +137,7 @@ export class ManifestRegistry {
     });
 
     // Register implicit intents. These are intents for capabilities which the application provides itself.
-    if (!proxy) {
-      this.registerIntents(symbolicName, capabilities.map(capability => ({type: capability.type, qualifier: capability.qualifier})), true);
-    }
+    this.registerIntents(symbolicName, capabilities.map(capability => ({type: capability.type, qualifier: capability.qualifier})), true);
   }
 
   /**
diff --git a/projects/scion/workbench-application-platform/src/lib/core/message-bus.service.ts b/projects/scion/workbench-application-platform/src/lib/core/message-bus.service.ts
index 15733c4f9..d7850e333 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/message-bus.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/message-bus.service.ts
@@ -16,7 +16,8 @@ import { Defined } from './defined.util';
 import { ManifestRegistry } from './manifest-registry.service';
 import { ApplicationRegistry } from './application-registry.service';
 import { UUID } from './uuid.util';
-import { Capability, CapabilityProviderMessage, Channel, IntentMessage, MessageEnvelope, PROTOCOL } from '@scion/workbench-application-platform.api';
+import { CapabilityProviderMessage, Channel, IntentMessage, MessageEnvelope, PROTOCOL } from '@scion/workbench-application-platform.api';
+import { matchesCapabilityQualifier } from './qualifier-tester';
 
 /**
  * Allows communication between the workbench applications.
@@ -137,16 +138,19 @@ export class MessageBus implements OnDestroy {
       filterByChannel('intent'),
       filter(envelope => {
         const message = envelope.message as IntentMessage;
-
-        // To receive the intent, the capability must have public visibility or provided by the intending application itself.
-        const selector = (capability: Capability): boolean => {
-          return !capability.private || this._manifestRegistry.isScopeCheckDisabled(symbolicName) || envelope.sender === symbolicName;
-        };
-
-        return this._manifestRegistry.hasCapability(symbolicName, message.type, message.qualifier, selector);
+        return this._manifestRegistry.getCapabilitiesByApplication(symbolicName)
+          .filter(capability => matchesCapabilityQualifier(capability.qualifier, message.qualifier))
+          .some(capability => this._manifestRegistry.isVisibleForApplication(capability, envelope.sender));
       }));
   }
 
+  /**
+   * Receives all intent messages.
+   */
+  public receiveIntents$(): Observable<MessageEnvelope<IntentMessage>> {
+    return this._stream$.pipe(filterByChannel('intent'));
+  }
+
   /**
    * Receives messages posted by applications providing a capability which the specified application has registered an intent for,
    * or if the receiving application is implicitly eligible because providing the capability itself.
@@ -163,12 +167,9 @@ export class MessageBus implements OnDestroy {
           return false;
         }
 
-        // To receive a message from a capability provider, the capability must have public visibility or provided by the application itself.
-        return envelope.sender === symbolicName
-          || this._manifestRegistry.isScopeCheckDisabled(symbolicName)
-          || this._manifestRegistry.getCapabilities(message.type, message.qualifier)
-            .filter(capability => capability.metadata.symbolicAppName === envelope.sender)
-            .some(capability => !capability.private);
+        return this._manifestRegistry.getCapabilities(message.type, message.qualifier)
+          .filter(capability => capability.metadata.symbolicAppName === envelope.sender)
+          .some(capability => this._manifestRegistry.isVisibleForApplication(capability, symbolicName));
       }));
   }
 
@@ -178,7 +179,7 @@ export class MessageBus implements OnDestroy {
   public receiveReplyMessagesForApplication$(symbolicName: string): Observable<MessageEnvelope> {
     return this._stream$.pipe(
       filterByChannel('reply'),
-      filter(envelope => envelope.replyTo === symbolicName)
+      filter(envelope => envelope.replyTo === symbolicName),
     );
   }
 
diff --git a/projects/scion/workbench-application-platform/src/lib/core/metadata.ts b/projects/scion/workbench-application-platform/src/lib/core/metadata.ts
index 78c773c11..2a24ab379 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/metadata.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/metadata.ts
@@ -9,8 +9,6 @@
  */
 
 import { InjectionToken } from '@angular/core';
-import { ApplicationRegistry } from './application-registry.service';
-import { ManifestRegistry } from './manifest-registry.service';
 import { Capability, Intent, IntentMessage, MessageEnvelope, Qualifier } from '@scion/workbench-application-platform.api';
 import { Observable } from 'rxjs';
 
@@ -101,7 +99,7 @@ export interface ApplicationManifest {
 /**
  * Handles intents of a specific type and qualifiers.
  *
- * There are some built-in handlers installed by the platform: 'view', 'popup', 'messagebox', 'notification' and 'manifest-registry'.
+ * There are some built-in handlers installed by the platform: 'messagebox', 'notification' and 'manifest-registry'.
  *
  * To install a handler, register it via DI token {INTENT_HANDLER} as multi provider in the host application.
  *
@@ -126,29 +124,19 @@ export interface IntentHandler {
   readonly type: string;
 
   /**
-   * Optional qualifiers which this handler requires. If not specified, {NilQualifier} is used.
+   * Qualifier which this handler requires. If not specified, {NilQualifier} is used.
    */
-  readonly qualifier?: Qualifier;
+  readonly qualifier: Qualifier;
 
   /**
    * Describes the capability this handler handles.
    */
   readonly description: string;
 
-  /**
-   * Indicates if this handler acts as a proxy through which intents are processed.
-   *
-   * For example, `ViewIntentHandler` is a proxy for application view capabilities which
-   * reads config from registered view capability providers and dispatches intents to the Angular router.
-   */
-  readonly proxy?: boolean;
-
   /**
    * A lifecycle hook that is called after the platform completed registration of applications.
-   *
-   * Use this method to handle any initialization tasks which require the application or manifest registry.
    */
-  onInit?(applicationRegistry: ApplicationRegistry, manifestRegistry: ManifestRegistry): void;
+  onInit?(): void;
 
   /**
    * Method invoked upon the receipt of an intent which this handler qualifies to receive.
diff --git a/projects/scion/workbench-application-platform/src/lib/core/qualifier-tester.ts b/projects/scion/workbench-application-platform/src/lib/core/qualifier-tester.ts
index f05684695..7c1c85e01 100644
--- a/projects/scion/workbench-application-platform/src/lib/core/qualifier-tester.ts
+++ b/projects/scion/workbench-application-platform/src/lib/core/qualifier-tester.ts
@@ -11,27 +11,75 @@
 import { NilQualifier, Qualifier } from '@scion/workbench-application-platform.api';
 
 /**
- * Tests if the qualifier matches the qualifier pattern.
+ * Tests if the given qualifier matches the capability qualifier.
  *
- * @param pattern
- *        qualifier as specified in the manifest, may contain wildcards as qualifier key or/and qualifier value;
- *        if `null`, {AnyQualifier} is used.
+ * @param capabilityQualifier
+ *        qualifier for a capability as specified in the manifest, may contain wildcards (* or ?) as qualifier value;
+ *        if `null`, {NilQualifier} is used.
  * @param testee
- *        the qualifier to match the pattern; must not contain wildcards
+ *        the qualifier to test against the capability qualifier; must not contain wildcards.
  */
-export function testQualifier(pattern: Qualifier, testee: Qualifier): boolean {
-  const _pattern = pattern || NilQualifier;
+export function matchesCapabilityQualifier(capabilityQualifier: Qualifier, testee: Qualifier): boolean {
+  const _capabilityQualifier = capabilityQualifier || NilQualifier;
   const _testee = testee || NilQualifier;
-  if (!_pattern.hasOwnProperty('*') && Object.keys(_pattern).sort().join(',') !== Object.keys(_testee).sort().join(',')) {
+  if (_capabilityQualifier.hasOwnProperty('*')) {
+    throw Error(`[IllegalCapabilityKeyError] Capability qualifiers do not support \`*\` as key`);
+  }
+
+  // Test if testee has all required entries
+  if (!Object.keys(_capabilityQualifier).every(key => _capabilityQualifier[key] === '?' || _testee.hasOwnProperty(key))) {
     return false;
   }
 
-  return Object.keys(_pattern)
+  // Test if testee has no additional entries
+  if (!Object.keys(_testee).every(key => _capabilityQualifier.hasOwnProperty(key))) {
+    return false;
+  }
+
+  return Object.keys(_capabilityQualifier)
+    .every(key => {
+      if (_capabilityQualifier[key] === '*') {
+        return _testee[key] !== undefined && _testee[key] !== null;
+      }
+      if (_capabilityQualifier[key] === '?') {
+        return true;
+      }
+      return _capabilityQualifier[key] === _testee[key];
+    });
+}
+
+/**
+ * Tests if the given qualifier matches the intent qualifier.
+ *
+ * @param intentQualifier
+ *        qualifier as specified in the manifest, may contain wildcards (*) as qualifier key or/and wildcards (* or ?) as qualifier value;
+ *        if `null`, {NilQualifier} is used.
+ * @param testee
+ *        the qualifier to test against the intent qualifier; must not contain wildcards
+ */
+export function matchesIntentQualifier(intentQualifier: Qualifier, testee: Qualifier): boolean {
+  const _intentQualifier = intentQualifier || NilQualifier;
+  const _testee = testee || NilQualifier;
+
+  // Test if testee has all required entries
+  if (!Object.keys(_intentQualifier).filter(key => key !== '*').every(key => _intentQualifier[key] === '?' || _testee.hasOwnProperty(key))) {
+    return false;
+  }
+
+  // Test if testee has no additional entries
+  if (!_intentQualifier.hasOwnProperty('*') && !Object.keys(_testee).every(key => _intentQualifier.hasOwnProperty(key))) {
+    return false;
+  }
+
+  return Object.keys(_intentQualifier)
     .filter(key => key !== '*')
     .every(key => {
-      if (_pattern[key] === '*') {
+      if (_intentQualifier[key] === '*') {
         return _testee[key] !== undefined && _testee[key] !== null;
       }
-      return _pattern[key] === _testee[key];
+      if (_intentQualifier[key] === '?') {
+        return true;
+      }
+      return _intentQualifier[key] === _testee[key];
     });
 }
diff --git a/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts b/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts
index cc1c6cf63..0943eb6a5 100644
--- a/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/manifest-capability/manifest-registry-intent-handler.service.ts
@@ -15,7 +15,7 @@ import { MessageBus } from '../core/message-bus.service';
 import { ApplicationRegistry } from '../core/application-registry.service';
 import { ManifestRegistry } from '../core/manifest-registry.service';
 import { Logger } from '../core/logger.service';
-import { testQualifier } from '../core/qualifier-tester';
+import { matchesIntentQualifier } from '../core/qualifier-tester';
 
 /**
  * Allows to query manifest registry.
@@ -88,8 +88,7 @@ export class ManifestRegistryIntentHandler implements IntentHandler {
     const intent: Intent = this._manifestRegistry.getIntent(intentId);
 
     const providers: Application[] = this._manifestRegistry.getCapabilities(intent.type, intent.qualifier)
-      .filter(capability => !capability.metadata.proxy)
-      .filter(capability => !capability.private || this._manifestRegistry.isScopeCheckDisabled(intent.metadata.symbolicAppName) || capability.metadata.symbolicAppName === intent.metadata.symbolicAppName)
+      .filter(capability => this._manifestRegistry.isVisibleForApplication(capability, intent.metadata.symbolicAppName))
       .map(capability => this._applicationRegistry.getApplication(capability.metadata.symbolicAppName));
     this._messageBus.publishReply(providers, envelope.sender, envelope.replyToUid);
   }
@@ -106,7 +105,7 @@ export class ManifestRegistryIntentHandler implements IntentHandler {
       const intents = this._manifestRegistry.getIntentsByApplication(application.symbolicName);
       const isConsumer = intents
         .filter(intent => !capability.private || this._manifestRegistry.isScopeCheckDisabled(intent.metadata.symbolicAppName) || intent.metadata.symbolicAppName === capability.metadata.symbolicAppName)
-        .some(intent => intent.type === capability.type && testQualifier(capability.qualifier, intent.qualifier));
+        .some(intent => intent.type === capability.type && matchesIntentQualifier(capability.qualifier, intent.qualifier));
       if (isConsumer) {
         consumers.push(application);
       }
@@ -125,8 +124,7 @@ export class ManifestRegistryIntentHandler implements IntentHandler {
     const qualifier: Qualifier = envelope.message.payload.qualifier;
 
     const capabilities: Capability[] = this._manifestRegistry.getCapabilities(type, qualifier)
-      .filter(capability => !capability.metadata.proxy)
-      .filter(capability => !capability.private || this._manifestRegistry.isScopeCheckDisabled(envelope.sender) || capability.metadata.symbolicAppName === envelope.sender)
+      .filter(capability => this._manifestRegistry.isVisibleForApplication(capability, envelope.sender))
       .filter(capability => this._manifestRegistry.hasIntent(envelope.sender, capability.type, capability.qualifier));
     this._messageBus.publishReply(capabilities, envelope.sender, envelope.replyToUid);
   }
@@ -152,8 +150,7 @@ export class ManifestRegistryIntentHandler implements IntentHandler {
       scopeCheckDisabled: application.scopeCheckDisabled,
       restrictions: application.restrictions,
       intents: this._manifestRegistry.getIntentsByApplication(application.symbolicName),
-      capabilities: this._manifestRegistry.getCapabilitiesByApplication(application.symbolicName)
-        .filter(capability => !capability.metadata.proxy),
+      capabilities: this._manifestRegistry.getCapabilitiesByApplication(application.symbolicName),
     };
   }
 }
diff --git a/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-capability.module.ts b/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-capability.module.ts
index fe60e7e7e..3069db816 100644
--- a/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-capability.module.ts
+++ b/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-capability.module.ts
@@ -8,13 +8,12 @@
  *  SPDX-License-Identifier: EPL-2.0
  */
 
-import { NgModule } from '@angular/core';
+import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
 import { CoreModule } from '../core/core.module';
 import { WorkbenchModule } from '@scion/workbench';
 import { CommonModule } from '@angular/common';
 import { PopupOutletComponent } from './popup-outlet.component';
-import { INTENT_HANDLER } from '../core/metadata';
-import { PopupIntentHandler } from './popup-intent-handler.service';
+import { PopupIntentDispatcher } from './popup-intent-dispatcher.service';
 
 /**
  * Built-in capability to show a popup.
@@ -29,7 +28,8 @@ import { PopupIntentHandler } from './popup-intent-handler.service';
     WorkbenchModule.forChild(),
   ],
   providers: [
-    {provide: INTENT_HANDLER, useClass: PopupIntentHandler, multi: true},
+    {provide: APP_INITIALIZER, useFactory: provideModuleInitializerFn, multi: true, deps: [Injector]},
+    PopupIntentDispatcher,
   ],
   entryComponents: [
     PopupOutletComponent,
@@ -37,3 +37,10 @@ import { PopupIntentHandler } from './popup-intent-handler.service';
 })
 export class PopupCapabilityModule {
 }
+
+export function provideModuleInitializerFn(injector: Injector): () => void {
+  // use injector because Angular Router cannot be injected in `APP_INITIALIZER` function
+  // do not return the function directly to not break the AOT build (add redundant assignment)
+  const fn = (): void => injector.get(PopupIntentDispatcher).init();
+  return fn;
+}
diff --git a/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-handler.service.ts b/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-dispatcher.service.ts
similarity index 74%
rename from projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-handler.service.ts
rename to projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-dispatcher.service.ts
index 4db683955..1cd6a3083 100644
--- a/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-handler.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/popup-capability/popup-intent-dispatcher.service.ts
@@ -10,13 +10,15 @@
 
 import { ElementRef, Inject, Injectable, OnDestroy, Renderer2, RendererFactory2 } from '@angular/core';
 import { PopupConfig, PopupService } from '@scion/workbench';
-import { IntentHandler } from '../core/metadata';
 import { ManifestRegistry } from '../core/manifest-registry.service';
 import { Logger } from '../core/logger.service';
 import { PopupInput, PopupOutletComponent } from './popup-outlet.component';
 import { MessageBus } from '../core/message-bus.service';
 import { DOCUMENT } from '@angular/common';
-import { AnyQualifier, IntentMessage, MessageEnvelope, PlatformCapabilityTypes, PopupCapability, PopupIntentMessage } from '@scion/workbench-application-platform.api';
+import { IntentMessage, MessageEnvelope, PlatformCapabilityTypes, PopupCapability, PopupIntentMessage } from '@scion/workbench-application-platform.api';
+import { filter, takeUntil } from 'rxjs/operators';
+import { Subject } from 'rxjs';
+import { ManifestCollector } from '../core/manifest-collector.service';
 
 /**
  * Shows a workbench popup for intents of the type 'popup'.
@@ -27,13 +29,9 @@ import { AnyQualifier, IntentMessage, MessageEnvelope, PlatformCapabilityTypes,
  * is looked up to provide metadata about the page to load in the popup.
  */
 @Injectable()
-export class PopupIntentHandler implements IntentHandler, OnDestroy {
-
-  public readonly type: PlatformCapabilityTypes = PlatformCapabilityTypes.Popup;
-  public readonly qualifier = AnyQualifier;
-  public readonly proxy = true;
-  public readonly description = 'Shows a workbench popup for capabilities of the type \'popup\'.';
+export class PopupIntentDispatcher implements OnDestroy {
 
+  private _destroy$ = new Subject<void>();
   private _renderer: Renderer2;
 
   constructor(private _logger: Logger,
@@ -41,11 +39,16 @@ export class PopupIntentHandler implements IntentHandler, OnDestroy {
               private _messageBus: MessageBus,
               private _popupService: PopupService,
               @Inject(DOCUMENT) private _document: any,
-              rendererFactory: RendererFactory2) {
-    this._renderer = rendererFactory.createRenderer(null, null);
+              rendererFactory: RendererFactory2,
+              private _manifestCollector: ManifestCollector) {
+      this._renderer = rendererFactory.createRenderer(null, null);
+  }
+
+  public init(): void {
+    this._manifestCollector.whenManifests.then(() => this.installIntentListener());
   }
 
-  public onIntent(envelope: MessageEnvelope<PopupIntentMessage>): void {
+  private onIntent(envelope: MessageEnvelope<PopupIntentMessage>): void {
     const intentMessage: PopupIntentMessage = envelope.message;
     const popupCapability = this.resolvePopupCapabilityProvider(envelope);
     if (!popupCapability) {
@@ -77,6 +80,21 @@ export class PopupIntentHandler implements IntentHandler, OnDestroy {
     });
   }
 
+  private installIntentListener(): void {
+    this._messageBus.receiveIntents$()
+      .pipe(
+        filter(envelope => envelope.message.type === PlatformCapabilityTypes.Popup),
+        takeUntil(this._destroy$),
+      )
+      .subscribe((envelope: MessageEnvelope<PopupIntentMessage>) => {
+        try {
+          this.onIntent(envelope);
+        } catch (error) {
+          this._logger.error(`Failed to handle intent [${JSON.stringify(envelope.message.qualifier || {})}]`, error);
+        }
+      });
+  }
+
   private createVirtualAnchorElement(outletBoundingBox: ClientRect | null, anchor: ClientRect): Element {
     const outletTop = outletBoundingBox && outletBoundingBox.top || 0;
     const outletLeft = outletBoundingBox && outletBoundingBox.left || 0;
@@ -95,15 +113,8 @@ export class PopupIntentHandler implements IntentHandler, OnDestroy {
 
   private resolvePopupCapabilityProvider(envelope: MessageEnvelope<IntentMessage>): PopupCapability {
     const qualifier = envelope.message.qualifier;
-    const popupCapabilities = this._manifestRegistry.getCapabilities<PopupCapability>(this.type, qualifier)
-      .filter(popupCapability => {
-        // Skip proxy providers (e.g. this implementor class)
-        return !popupCapability.metadata.proxy;
-      })
-      .filter(popupCapability => {
-        // Skip if the capability has private visibility and the intending application does not provide the view capability itself
-        return !popupCapability.private || this._manifestRegistry.isScopeCheckDisabled(envelope.sender) || envelope.sender === popupCapability.metadata.symbolicAppName;
-      });
+    const popupCapabilities = this._manifestRegistry.getCapabilities<PopupCapability>(PlatformCapabilityTypes.Popup, qualifier)
+      .filter(capability => this._manifestRegistry.isVisibleForApplication(capability, envelope.sender));
 
     if (popupCapabilities.length === 0) {
       this._logger.error(`[IllegalStateError] No capability registered matching the qualifier '${JSON.stringify(qualifier || {})}'.`, popupCapabilities);
@@ -119,5 +130,6 @@ export class PopupIntentHandler implements IntentHandler, OnDestroy {
 
   public ngOnDestroy(): void {
     this._renderer.destroy();
+    this._destroy$.next();
   }
 }
diff --git a/projects/scion/workbench-application-platform/src/lib/spec/array.util.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/array.util.spec.ts
new file mode 100644
index 000000000..5bbba36a0
--- /dev/null
+++ b/projects/scion/workbench-application-platform/src/lib/spec/array.util.spec.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018-2019 Swiss Federal Railways
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ */
+
+import { Arrays } from '../core/array.util';
+
+describe('Arrays', () => {
+
+  describe('Arrays.equal', () => {
+
+    it('should be equal for same array references', () => {
+      const array = ['a', 'b', 'c'];
+      expect(Arrays.equal(array, array)).toBeTruthy();
+    });
+
+    it('should be equal for same elements (same order)', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'b', 'c'];
+      expect(Arrays.equal(array1, array2)).toBeTruthy();
+    });
+
+    it('should be equal for same elements (unordered)', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'c', 'b'];
+      expect(Arrays.equal(array1, array2, false)).toBeTruthy();
+    });
+
+    it('should not be equal for different elements (1)', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'b', 'c', 'e'];
+      expect(Arrays.equal(array1, array2)).toBeFalsy();
+    });
+
+    it('should not be equal for different elements (2)', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'B', 'c'];
+      expect(Arrays.equal(array1, array2)).toBeFalsy();
+    });
+
+    it('should not be equal if ordered differently', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'c', 'b'];
+      expect(Arrays.equal(array1, array2)).toBeFalsy();
+    });
+
+    it('should be equal if ordered differently', () => {
+      const array1 = ['a', 'b', 'c'];
+      const array2 = ['a', 'c', 'b'];
+      expect(Arrays.equal(array1, array2, false)).toBeTruthy();
+    });
+  });
+
+  describe('Arrays.last', () => {
+
+    it('should find the last item matching the predicate', () => {
+      const array = ['a', 'b', 'c', 'd', 'e'];
+      expect(Arrays.last(array, (item: string): boolean => item === 'c')).toEqual('c');
+    });
+
+    it('should return `undefined` if no element matches the predicate', () => {
+      const array = ['a', 'b', 'c', 'd', 'e'];
+      expect(Arrays.last(array, () => false)).toBeUndefined();
+    });
+
+    it('should return the last item in the array if no predicate is specified', () => {
+      const array = ['a', 'b', 'c', 'd', 'e'];
+      expect(Arrays.last(array)).toEqual('e');
+    });
+
+    it('should return `undefined` if the array is empty', () => {
+      expect(Arrays.last([])).toBeUndefined();
+      expect(Arrays.last([], () => true)).toBeUndefined();
+    });
+  });
+
+  describe('Arrays.remove', () => {
+
+    it('should remove the specified element', () => {
+      const array = ['a', 'b', 'c', 'd', 'e'];
+      expect(Arrays.remove(array, 'c')).toEqual(['a', 'b', 'd', 'e']);
+    });
+
+    it('should not modify the original array', () => {
+      const array = ['a', 'b', 'c', 'd', 'e'];
+      expect(Arrays.remove(array, 'c')).toEqual(['a', 'b', 'd', 'e']);
+      expect(array).toEqual(['a', 'b', 'c', 'd', 'e']);
+    });
+  });
+});
diff --git a/projects/scion/workbench-application-platform/src/lib/spec/manifest-registry.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/manifest-registry.spec.ts
index 39d471cce..5183801c8 100644
--- a/projects/scion/workbench-application-platform/src/lib/spec/manifest-registry.spec.ts
+++ b/projects/scion/workbench-application-platform/src/lib/spec/manifest-registry.spec.ts
@@ -42,15 +42,6 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.isHandled('app-2', type, qualifier)).toBeFalsy();
       expect(manifestRegistry.isHandled('app-3', type, qualifier)).toBeTruthy();
     })));
-
-    it('should hide proxy capability providers from any application', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
-      const type = PlatformCapabilityTypes.View;
-      const qualifier: Qualifier = {entity: 'entity'};
-      manifestRegistry.registerCapability('app-1', [{type, qualifier, private: false}], true);
-
-      expect(manifestRegistry.isHandled('app-1', type, qualifier)).toBeFalsy();
-      expect(manifestRegistry.isHandled('app-2', type, qualifier)).toBeFalsy();
-    })));
   });
 
   describe('function \'hasCapability(...)\'', () => {
@@ -61,7 +52,7 @@ describe('ManifestRegistry', () => {
       const type3 = 'type-3';
       const qualifier1: Qualifier = {entity: 'entity', qualifier: 1};
       const qualifier2: Qualifier = {entity: 'entity', qualifier: 2};
-      const qualifier3: Qualifier = {'*': '*'};
+      const qualifier3: Qualifier = {entity: '?'};
       manifestRegistry.registerCapability('app-1', [{type: type1, qualifier: qualifier1, private: false}]);
       manifestRegistry.registerCapability('app-1', [{type: type2, qualifier: qualifier2, private: true}]);
       manifestRegistry.registerCapability('app-1', [{type: type3, qualifier: qualifier3, private: false}]);
@@ -69,8 +60,6 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.hasCapability('app-1', type1, qualifier1)).toBeTruthy();
       expect(manifestRegistry.hasCapability('app-1', type2, qualifier2)).toBeTruthy();
       expect(manifestRegistry.hasCapability('app-1', type3, {})).toBeTruthy();
-      expect(manifestRegistry.hasCapability('app-1', type3, {}, () => true)).toBeTruthy();
-      expect(manifestRegistry.hasCapability('app-1', type3, {}, () => false)).toBeFalsy();
       expect(manifestRegistry.hasCapability('app-1', 'other-type', {})).toBeFalsy();
 
       expect(manifestRegistry.hasCapability('app-2', type1, qualifier1)).toBeFalsy();
@@ -91,7 +80,7 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.hasCapability('app-2', type, undefined)).toBeFalsy();
     })));
 
-    it('should return `true` if a qualifier matches the qualifier pattern', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
+    it('should return `true` if a qualifier matches the qualifier pattern (*)', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
       const type = 'type';
       manifestRegistry.registerCapability('app-1', [{type, qualifier: {entity: '*'}, private: false}]);
 
@@ -105,6 +94,29 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.hasCapability('app-2', type, {entity: 'entity-2'})).toBeFalsy();
       expect(manifestRegistry.hasCapability('app-2', type, {entity: 'entity-2', name: 'smith'})).toBeFalsy();
     })));
+
+    it('should return `true` if a qualifier matches the qualifier pattern (?)', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
+      const type = 'type';
+      manifestRegistry.registerCapability('app-1', [{type, qualifier: {entity: '?'}, private: false}]);
+
+      expect(manifestRegistry.hasCapability('app-1', type, null)).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, undefined)).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {})).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {entity: null})).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {entity: undefined})).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {entity: 'optional-entity-1'})).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {entity: 'optional-entity-2'})).toBeTruthy();
+      expect(manifestRegistry.hasCapability('app-1', type, {entity: 'optional-entity-2', name: 'smith'})).toBeFalsy();
+
+      expect(manifestRegistry.hasCapability('app-2', type, null)).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, undefined)).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {})).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {entity: null})).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {entity: undefined})).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {entity: 'optional-entity-1'})).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {entity: 'optional-entity-2'})).toBeFalsy();
+      expect(manifestRegistry.hasCapability('app-2', type, {entity: 'optional-entity-2', name: 'smith'})).toBeFalsy();
+    })));
   });
 
   describe('function \'hasIntent(...)\'', () => {
@@ -167,7 +179,7 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.hasIntent('app-2', type, undefined)).toBeTruthy();
     })));
 
-    it('should return `false` if `null` or `undefined` is given as wildcard intent value', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
+    it('should return `false` if `null` or `undefined` is given as wildcard (*) intent value', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
       const type = 'type';
       manifestRegistry.registerIntents('app-2', [{type, qualifier: {q: '*'}}]);
 
@@ -175,6 +187,16 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.hasIntent('app-2', type, {q: undefined})).toBeFalsy();
     })));
 
+    it('should return `true` if `null` or `undefined` is given as wildcard (?) intent value', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
+      const type = 'type';
+      manifestRegistry.registerIntents('app-2', [{type, qualifier: {q: '?'}}]);
+
+      expect(manifestRegistry.hasIntent('app-2', type, {q: null})).toBeTruthy();
+      expect(manifestRegistry.hasIntent('app-2', type, {q: undefined})).toBeTruthy();
+      expect(manifestRegistry.hasIntent('app-2', type, {})).toBeTruthy();
+      expect(manifestRegistry.hasIntent('app-2', type, null)).toBeTruthy();
+      expect(manifestRegistry.hasIntent('app-2', type, undefined)).toBeTruthy();
+    })));
   });
 
   describe('function \'getCapabilities(...)\'', () => {
@@ -183,6 +205,7 @@ describe('ManifestRegistry', () => {
       manifestRegistry.registerCapability('app-1', [{type: 'type-1'}]);
       manifestRegistry.registerCapability('app-1', [{type: 'type-2', qualifier: {entity: 'entity'}}]);
       manifestRegistry.registerCapability('app-1', [{type: 'type-3', qualifier: {entity: '*'}}]);
+      manifestRegistry.registerCapability('app-1', [{type: 'type-4', qualifier: {entity: '?'}}]);
 
       expect(manifestRegistry.getCapabilities('type-1', null).length).toBe(1);
       expect(manifestRegistry.getCapabilities('type-1', {}).length).toBe(1);
@@ -200,6 +223,13 @@ describe('ManifestRegistry', () => {
       expect(manifestRegistry.getCapabilities('type-3', {'entity': null}).length).toBe(0);
       expect(manifestRegistry.getCapabilities('type-3', {'entity': 'other-entity'}).length).toBe(1);
       expect(manifestRegistry.getCapabilities('type-3', {'some': 'qualifier'}).length).toBe(0);
+
+      expect(manifestRegistry.getCapabilities('type-4', null).length).toBe(1);
+      expect(manifestRegistry.getCapabilities('type-4', {}).length).toBe(1);
+      expect(manifestRegistry.getCapabilities('type-4', {'entity': 'entity'}).length).toBe(1);
+      expect(manifestRegistry.getCapabilities('type-4', {'entity': null}).length).toBe(1);
+      expect(manifestRegistry.getCapabilities('type-4', {'entity': 'other-entity'}).length).toBe(1);
+      expect(manifestRegistry.getCapabilities('type-4', {'some': 'qualifier'}).length).toBe(0);
     })));
   });
 
@@ -219,6 +249,21 @@ describe('ManifestRegistry', () => {
     })));
   });
 
+  describe('function \'getCapabilitiesByPredicate(...)\'', () => {
+
+    it('should return capabilities matching the given predicate', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
+      manifestRegistry.registerCapability('app-1', [{type: 'type-1', private: false}]);
+      manifestRegistry.registerCapability('app-1', [{type: 'type-2', qualifier: {entity: 'entity'}, private: false}]);
+      manifestRegistry.registerCapability('app-1', [{type: 'type-3', qualifier: {entity: '*'}, private: false}]);
+      manifestRegistry.registerCapability('app-1', [{type: 'type-4', qualifier: {entity: '?'}, private: true}]);
+
+      expect(manifestRegistry.getCapabilitiesByPredicate(() => true).length).toBe(4);
+      expect(manifestRegistry.getCapabilitiesByPredicate(capability => !capability.private).length).toBe(3);
+      expect(manifestRegistry.getCapabilitiesByPredicate(capability => capability.type === 'type-3').length).toBe(1);
+      expect(manifestRegistry.getCapabilitiesByPredicate(capability => capability.qualifier && capability.qualifier.hasOwnProperty('entity')).length).toBe(3);
+    })));
+  });
+
   describe('function \'getIntentsByApplication(...)\'', () => {
 
     it('should return intents by application', fakeAsync(inject([ManifestRegistry], (manifestRegistry: ManifestRegistry) => {
diff --git a/projects/scion/workbench-application-platform/src/lib/spec/message-bus.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/message-bus.spec.ts
index 35c502b65..671113367 100644
--- a/projects/scion/workbench-application-platform/src/lib/spec/message-bus.spec.ts
+++ b/projects/scion/workbench-application-platform/src/lib/spec/message-bus.spec.ts
@@ -213,13 +213,13 @@ describe('MessageBus', () => {
         name: 'app-1', capability: [{type: PlatformCapabilityTypes.View, private: false}],
       });
       registerApp({
-        name: 'app-2', capability: [{type: PlatformCapabilityTypes.View, qualifier: {'*': '*'}, private: false}],
+        name: 'app-2', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: '*', viewType: '*'}, private: false}],
       });
       registerApp({
-        name: 'app-3', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'person', '*': '*'}, private: false}],
+        name: 'app-3', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: '?', viewType: '?'}, private: false}],
       });
       registerApp({
-        name: 'app-4', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'company', '*': '*'}, private: false}],
+        name: 'app-4', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'company', viewType: 'company-info'}, private: false}],
       });
       registerApp({
         name: 'app-5', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'person', viewType: '*'}, private: false}],
@@ -265,7 +265,7 @@ describe('MessageBus', () => {
       expect(messageDispatched).toBeTruthy();
     })));
 
-    it('should be published if the publishing app manifests a matching wildcard capability', fakeAsync(inject([MessageBus], (messageBus: MessageBus) => {
+    it('should be published if the publishing app manifests a matching wildcard (*) capability', fakeAsync(inject([MessageBus], (messageBus: MessageBus) => {
       registerApp({
         name: 'app-1', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'person', viewType: '*'}, private: false}],
       });
@@ -275,6 +275,16 @@ describe('MessageBus', () => {
       expect(messageDispatched).toBeTruthy();
     })));
 
+    it('should be published if the publishing app manifests a matching wildcard (?) capability', fakeAsync(inject([MessageBus], (messageBus: MessageBus) => {
+      registerApp({
+        name: 'app-1', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'person', viewType: '?'}, private: false}],
+      });
+
+      const envelope = createCapabilityEnvelope({type: PlatformCapabilityTypes.View, qualifier: {entity: 'person'}});
+      messageBus.publishMessageIfQualified(envelope, 'app-1');
+      expect(messageDispatched).toBeTruthy();
+    })));
+
     it('should not be published if the publishing app does not manifest a respective capability [NotQualifiedError]', fakeAsync(inject([MessageBus], (messageBus: MessageBus) => {
       registerApp({
         name: 'app-1', capability: [{type: PlatformCapabilityTypes.View, qualifier: {entity: 'person', viewType: 'personal-info'}, private: false}],
diff --git a/projects/scion/workbench-application-platform/src/lib/spec/qualifier.spec.ts b/projects/scion/workbench-application-platform/src/lib/spec/qualifier.spec.ts
index c41cb2478..58cc43dca 100644
--- a/projects/scion/workbench-application-platform/src/lib/spec/qualifier.spec.ts
+++ b/projects/scion/workbench-application-platform/src/lib/spec/qualifier.spec.ts
@@ -8,64 +8,149 @@
  *  SPDX-License-Identifier: EPL-2.0
  */
 
-import { testQualifier } from '../core/qualifier-tester';
+import { matchesCapabilityQualifier, matchesIntentQualifier } from '../core/qualifier-tester';
 
 describe('Qualifier', () => {
 
-  describe('function \'testQualifier(...)\'', () => {
+  describe('function \'matchesCapabilityQualifier(...)\'', () => {
 
     it('tests strict equality', () => {
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42})).toBeTruthy();
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'person', id: '42'})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'person', id: 43})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'company', id: 42})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'person'})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: 42}, null)).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'person', id: '42'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'person', id: 43})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'person'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: 42}, null)).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', flag: true}, {entity: 'person', flag: true})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', flag: true}, {entity: 'person', flag: 'true'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', flag: true}, {entity: 'person', flag: false})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', flag: false}, {entity: 'person', flag: 'false'})).toBeFalsy();
     });
 
-    it('supports value wildcards', () => {
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42})).toBeTruthy();
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'person', id: '42'})).toBeTruthy();
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 43})).toBeTruthy();
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'company', id: 42})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'person'})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
-      expect(testQualifier({entity: 'person', id: '*'}, null)).toBeFalsy();
-
-      expect(testQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: 'b', c: 'c'})).toBeTruthy();
-      expect(testQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: null, c: 'c'})).toBeFalsy();
-      expect(testQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c'})).toBeFalsy();
-      expect(testQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c'})).toBeFalsy();
-      expect(testQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c', d: 'd'})).toBeFalsy();
+    it('supports value wildcards (*)', () => {
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'person', id: '42'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 43})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'person'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '*'}, null)).toBeFalsy();
+
+      expect(matchesCapabilityQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: 'b', c: 'c'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: null, c: 'c'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c', d: 'd'})).toBeFalsy();
+    });
+
+    it('supports value wildcards (?)', () => {
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'person', id: '42'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 43})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'person'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesCapabilityQualifier({entity: 'person', id: '?'}, null)).toBeFalsy();
+
+      expect(matchesCapabilityQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', b: 'b', c: 'c'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', b: null, c: 'c'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', c: 'c'})).toBeTruthy();
+      expect(matchesCapabilityQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', c: 'c', d: 'd'})).toBeFalsy();
+    });
+
+    it('accepts only empty qualifiers', () => {
+      expect(matchesCapabilityQualifier({}, {})).toBeTruthy();
+      expect(matchesCapabilityQualifier({}, null)).toBeTruthy();
+      expect(matchesCapabilityQualifier({}, {entity: 'person'})).toBeFalsy();
+    });
+
+    it('throws IllegalKeyError if qualifier key is \'*\'', () => {
+      const errorMatcher = /IllegalCapabilityKeyError/;
+      expect(() => matchesCapabilityQualifier({'*': '*'}, {})).toThrowError(errorMatcher);
+      expect(() => matchesCapabilityQualifier({'*': '*'}, null)).toThrowError(errorMatcher);
+      expect(() => matchesCapabilityQualifier({'*': '*'}, {entity: 'person'})).toThrowError(errorMatcher);
+    });
+
+    it('accepts only empty qualifiers if not providing a pattern qualifier', () => {
+      expect(matchesCapabilityQualifier(null, {})).toBeTruthy();
+      expect(matchesCapabilityQualifier(null, null)).toBeTruthy();
+      expect(matchesCapabilityQualifier(null, {entity: 'person'})).toBeFalsy();
+    });
+  });
+
+  describe('function \'matchesIntentQualifier(...)\'', () => {
+
+    it('tests strict equality', () => {
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'person', id: '42'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'person', id: 43})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'person'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: 42}, null)).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', flag: true}, {entity: 'person', flag: true})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', flag: true}, {entity: 'person', flag: 'true'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', flag: true}, {entity: 'person', flag: false})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', flag: false}, {entity: 'person', flag: 'false'})).toBeFalsy();
+    });
+
+    it('supports value wildcards (*)', () => {
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'person', id: '42'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 43})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'person'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: '*'}, null)).toBeFalsy();
+
+      expect(matchesIntentQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: 'b', c: 'c'})).toBeTruthy();
+      expect(matchesIntentQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', b: null, c: 'c'})).toBeFalsy();
+      expect(matchesIntentQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c'})).toBeFalsy();
+      expect(matchesIntentQualifier({a: 'a', b: '*', c: 'c'}, {a: 'a', c: 'c', d: 'd'})).toBeFalsy();
+    });
+
+    it('supports value wildcards (?)', () => {
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'person', id: '42'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 43})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'person'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, {entity: 'person', id: 42, name: 'smith'})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', id: '?'}, null)).toBeFalsy();
+
+      expect(matchesIntentQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', b: 'b', c: 'c'})).toBeTruthy();
+      expect(matchesIntentQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', b: null, c: 'c'})).toBeTruthy();
+      expect(matchesIntentQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', b: undefined, c: 'c'})).toBeTruthy();
+      expect(matchesIntentQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', c: 'c'})).toBeTruthy();
+      expect(matchesIntentQualifier({a: 'a', b: '?', c: 'c'}, {a: 'a', c: 'c', d: 'd'})).toBeFalsy();
     });
 
-    it('supports key wildcards', () => {
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 42})).toBeTruthy();
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: '42'})).toBeTruthy();
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 43})).toBeTruthy();
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'company', id: 42})).toBeFalsy();
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'person'})).toBeTruthy();
-      expect(testQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 42, name: 'smith'})).toBeTruthy();
-      expect(testQualifier({entity: 'person', '*': '*'}, null)).toBeFalsy();
+    it('supports key wildcards (*)', () => {
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 42})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: '42'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 43})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'company', id: 42})).toBeFalsy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'person'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, {entity: 'person', id: 42, name: 'smith'})).toBeTruthy();
+      expect(matchesIntentQualifier({entity: 'person', '*': '*'}, null)).toBeFalsy();
     });
 
     it('accepts only empty qualifiers', () => {
-      expect(testQualifier({}, {})).toBeTruthy();
-      expect(testQualifier({}, null)).toBeTruthy();
-      expect(testQualifier({}, {entity: 'person'})).toBeFalsy();
+      expect(matchesIntentQualifier({}, {})).toBeTruthy();
+      expect(matchesIntentQualifier({}, null)).toBeTruthy();
+      expect(matchesIntentQualifier({}, {entity: 'person'})).toBeFalsy();
     });
 
-    it('accepts any qualifier is providing a wildcard qualifier', () => {
-      expect(testQualifier({'*': '*'}, {})).toBeTruthy();
-      expect(testQualifier({'*': '*'}, null)).toBeTruthy();
-      expect(testQualifier({'*': '*'}, {entity: 'person'})).toBeTruthy();
+    it('accepts any qualifier if providing a wildcard qualifier', () => {
+      expect(matchesIntentQualifier({'*': '*'}, {})).toBeTruthy();
+      expect(matchesIntentQualifier({'*': '*'}, null)).toBeTruthy();
+      expect(matchesIntentQualifier({'*': '*'}, {entity: 'person'})).toBeTruthy();
     });
 
     it('accepts only empty qualifiers if not providing a pattern qualifier', () => {
-      expect(testQualifier(null, {})).toBeTruthy();
-      expect(testQualifier(null, null)).toBeTruthy();
-      expect(testQualifier(null, {entity: 'person'})).toBeFalsy();
+      expect(matchesIntentQualifier(null, {})).toBeTruthy();
+      expect(matchesIntentQualifier(null, null)).toBeTruthy();
+      expect(matchesIntentQualifier(null, {entity: 'person'})).toBeFalsy();
     });
   });
 });
diff --git a/projects/scion/workbench-application-platform/src/lib/view-capability/view-capability.module.ts b/projects/scion/workbench-application-platform/src/lib/view-capability/view-capability.module.ts
index 4f75024eb..24d016a68 100644
--- a/projects/scion/workbench-application-platform/src/lib/view-capability/view-capability.module.ts
+++ b/projects/scion/workbench-application-platform/src/lib/view-capability/view-capability.module.ts
@@ -8,14 +8,13 @@
  *  SPDX-License-Identifier: EPL-2.0
  */
 
-import { NgModule } from '@angular/core';
+import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
 import { CoreModule } from '../core/core.module';
-import { ViewIntentHandler } from './view-intent-handler.service';
+import { ViewIntentDispatcher } from './view-intent-dispatcher.service';
 import { WorkbenchModule } from '@scion/workbench';
 import { RouterModule } from '@angular/router';
 import { CommonModule } from '@angular/common';
 import { ViewOutletComponent } from './view-outlet.component';
-import { INTENT_HANDLER } from '../core/metadata';
 
 /**
  * Built-in capability to show a view.
@@ -31,7 +30,8 @@ import { INTENT_HANDLER } from '../core/metadata';
     RouterModule.forChild([]),
   ],
   providers: [
-    {provide: INTENT_HANDLER, useClass: ViewIntentHandler, multi: true},
+    {provide: APP_INITIALIZER, useFactory: provideModuleInitializerFn, multi: true, deps: [Injector]},
+    ViewIntentDispatcher,
   ],
   entryComponents: [
     ViewOutletComponent,
@@ -39,3 +39,10 @@ import { INTENT_HANDLER } from '../core/metadata';
 })
 export class ViewCapabilityModule {
 }
+
+export function provideModuleInitializerFn(injector: Injector): () => void {
+  // use injector because Angular Router cannot be injected in `APP_INITIALIZER` function
+  // do not return the function directly to not break the AOT build (add redundant assignment)
+  const fn = (): void => injector.get(ViewIntentDispatcher).init();
+  return fn;
+}
diff --git a/projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-handler.service.ts b/projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-dispatcher.service.ts
similarity index 71%
rename from projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-handler.service.ts
rename to projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-dispatcher.service.ts
index 7d31f19b1..1a42104fd 100644
--- a/projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-handler.service.ts
+++ b/projects/scion/workbench-application-platform/src/lib/view-capability/view-intent-dispatcher.service.ts
@@ -8,17 +8,20 @@
  *  SPDX-License-Identifier: EPL-2.0
  */
 
-import { Injectable, Type } from '@angular/core';
+import { Injectable, OnDestroy, Type } from '@angular/core';
 import { VIEW_CAPABILITY_ID_PARAM, VIEW_PATH_PARAM } from './metadata';
 import { Router } from '@angular/router';
 import { ViewOutletComponent } from './view-outlet.component';
 import { WbNavigationExtras, WorkbenchActivityPartService, WorkbenchAuxiliaryRoutesRegistrator, WorkbenchRouter, WorkbenchService, WorkbenchView } from '@scion/workbench';
-import { noop } from 'rxjs';
-import { IntentHandler } from '../core/metadata';
+import { noop, Subject } from 'rxjs';
 import { ApplicationRegistry } from '../core/application-registry.service';
 import { ManifestRegistry } from '../core/manifest-registry.service';
 import { Url } from '../core/url.util';
-import { AnyQualifier, MessageEnvelope, PlatformCapabilityTypes, Qualifier, ViewCapability, ViewIntentMessage } from '@scion/workbench-application-platform.api';
+import { MessageEnvelope, PlatformCapabilityTypes, Qualifier, ViewCapability, ViewIntentMessage } from '@scion/workbench-application-platform.api';
+import { filter, takeUntil } from 'rxjs/operators';
+import { MessageBus } from '../core/message-bus.service';
+import { Logger } from '../core/logger.service';
+import { ManifestCollector } from '../core/manifest-collector.service';
 
 /**
  * Opens a workbench view for intents of the type 'view'.
@@ -29,40 +32,33 @@ import { AnyQualifier, MessageEnvelope, PlatformCapabilityTypes, Qualifier, View
  * is looked up to provide metadata about the page to navigate to.
  */
 @Injectable()
-export class ViewIntentHandler implements IntentHandler {
+export class ViewIntentDispatcher implements OnDestroy {
 
-  public readonly type: PlatformCapabilityTypes = PlatformCapabilityTypes.View;
-  public readonly qualifier = AnyQualifier;
-  public readonly proxy = true;
-  public readonly description = 'Open a workbench view for capabilities of the type \'view\'.';
-
-  private _applicationRegistry: ApplicationRegistry;
-  private _manifestRegistry: ManifestRegistry;
+  private _destroy$ = new Subject<void>();
 
   constructor(private _workbench: WorkbenchService,
               private _router: Router,
               private _wbRouter: WorkbenchRouter,
               private _routesRegistrator: WorkbenchAuxiliaryRoutesRegistrator,
-              private _activityPartService: WorkbenchActivityPartService) {
+              private _activityPartService: WorkbenchActivityPartService,
+              private _applicationRegistry: ApplicationRegistry,
+              private _manifestRegistry: ManifestRegistry,
+              private _messageBus: MessageBus,
+              private _logger: Logger,
+              private _manifestCollector: ManifestCollector) {
   }
 
-  public onInit(applicationRegistry: ApplicationRegistry, manifestRegistry: ManifestRegistry): void {
-    this._applicationRegistry = applicationRegistry;
-    this._manifestRegistry = manifestRegistry;
-    this.installViewCapabilityRoutes();
-    this.addToActivityPanel();
+  public init(): void {
+    this._manifestCollector.whenManifests.then(() => {
+      this.installViewCapabilityRoutes();
+      this.installIntentListener();
+      this.addToActivityPanel();
+    });
   }
 
-  public onIntent(envelope: MessageEnvelope<ViewIntentMessage>): void {
-    this._manifestRegistry.getCapabilities<ViewCapability>(this.type, envelope.message.qualifier)
-      .filter(viewCapability => {
-        // Skip proxy providers (e.g. this implementor class)
-        return !viewCapability.metadata.proxy;
-      })
-      .filter(viewCapability => {
-        // Skip if the capability has private visibility and the intending application does not provide the view capability itself
-        return !viewCapability.private || this._manifestRegistry.isScopeCheckDisabled(envelope.sender) || envelope.sender === viewCapability.metadata.symbolicAppName;
-      })
+  private onIntent(envelope: MessageEnvelope<ViewIntentMessage>): void {
+    this._manifestRegistry.getCapabilities<ViewCapability>(PlatformCapabilityTypes.View, envelope.message.qualifier)
+      .filter(capability => this._manifestRegistry.isVisibleForApplication(capability, envelope.sender))
       .forEach((viewCapability: ViewCapability) => {
         const intentMessage: ViewIntentMessage = envelope.message;
         const view = envelope._injector.get(WorkbenchView as Type<WorkbenchView>, null); // TODO [Angular 9]: remove type cast for abstract symbols once 'angular/issues/29905' and 'angular/issues/23611' are fixed
@@ -86,6 +82,21 @@ export class ViewIntentHandler implements IntentHandler {
       });
   }
 
+  private installIntentListener(): void {
+    this._messageBus.receiveIntents$()
+      .pipe(
+        filter(envelope => envelope.message.type === PlatformCapabilityTypes.View),
+        takeUntil(this._destroy$),
+      )
+      .subscribe((envelope: MessageEnvelope<ViewIntentMessage>) => {
+        try {
+          this.onIntent(envelope);
+        } catch (error) {
+          this._logger.error(`Failed to handle intent [${JSON.stringify(envelope.message.qualifier || {})}]`, error);
+        }
+      });
+  }
+
   private installViewCapabilityRoutes(): void {
     // Register capability routes as primary routes. Auxiliary routes are registered when the views are opened (by the workbench)
     this._routesRegistrator.replaceRouterConfig([
@@ -100,8 +111,7 @@ export class ViewIntentHandler implements IntentHandler {
   }
 
   private addToActivityPanel(): void {
-    this._manifestRegistry.getCapabilitiesByType<ViewCapability>(this.type)
-      .filter(viewCapability => !viewCapability.metadata.proxy)
+    this._manifestRegistry.getCapabilitiesByType<ViewCapability>(PlatformCapabilityTypes.View)
       .filter(viewCapability => viewCapability.properties.activityItem)
       .forEach((viewCapability: ViewCapability) => {
         const activityItem = viewCapability.properties.activityItem;
@@ -133,5 +143,9 @@ export class ViewIntentHandler implements IntentHandler {
       ...(matrixParamObject ? [matrixParamObject] : []),
     ];
   }
+
+  public ngOnDestroy(): void {
+    this._destroy$.next();
+  }
 }
 
diff --git a/projects/scion/workbench-application.core/src/lib/manifest-registry.service.ts b/projects/scion/workbench-application.core/src/lib/manifest-registry.service.ts
index d1241fc86..dd142e78a 100644
--- a/projects/scion/workbench-application.core/src/lib/manifest-registry.service.ts
+++ b/projects/scion/workbench-application.core/src/lib/manifest-registry.service.ts
@@ -106,7 +106,7 @@ export class ManifestRegistryService implements Service {
   /**
    * Queries the manifest registry for capabilities of given type and qualifier.
    *
-   * There are ony capabilities returned for which the requesting application has manifested an intent.
+   * There are only capabilities returned for which the requesting application has manifested an intent.
    */
   public capabilities$(type: string, qualifier: Qualifier): Observable<Capability[]> {
     const intentMessage: ManifestRegistryIntentMessages.FindCapabilities = {
diff --git a/resources/site/getting-started/workbench-application-platform/getting-started.md b/resources/site/getting-started/workbench-application-platform/getting-started.md
index 09a81cdda..f3462a9a3 100644
--- a/resources/site/getting-started/workbench-application-platform/getting-started.md
+++ b/resources/site/getting-started/workbench-application-platform/getting-started.md
@@ -49,10 +49,13 @@ To integrate an application, it should provide one or more entry points. When on
 The platform provides the concept of an application manifest to facilitate this entry-point invocation paradigm. Thus every application has a manifest which lists its capabilities and intents. Typically, the manifest is deployed as part of the application.
 
 #### Capability
-A capability represents a feature which an application provides. It is of a specific type and has assigned a qualifier. The qualifier is used for logical addressing so that other applications can invoke it without knowing the provider nor the relevant entry point, if any. A qualifier is a dictionary of key-value pairs. It is allowed to use the wildcard character (*) as qualifier value or as qualifier key.
+A capability represents a feature which an application provides. It is of a specific type and has assigned a qualifier. The qualifier is used for logical addressing so that other applications can invoke it without knowing the provider nor the relevant entry point, if any. A qualifier is a dictionary of key-value pairs. It is allowed to use the wildcard characters (\*) and (?) as qualifier value. (\*) means that some value has to be provided, whereas for (?) the entry value is optional.
 
 > For example, if an application provides a view to editing some personal data, the qualifier could look as follows: `{entity: 'person', id: '*'}`.\
-Other applications can invoke this capability by issuing an intent matching the capability's qualifier, with any value allowed for the 'id'.
+Applications can invoke this capability by issuing an intent matching the capability's qualifier, with any value allowed for the 'id'.\
+
+> For example, if the application provides a single view to edit or create a person, the qualifier could look as follows: `{entity: 'person', id: '?'}`.\
+Applications can invoke this capability by issuing an intent without an id for creation (`{entity: 'person'}`), or with an id for editing (`{entity: 'person', id: 1}`).
 
 Some capabilities define an entry point URL if showing an application page. Using placeholders in URL segments is possible. When invoked, they are replaced with values from the intent qualifier.
 
@@ -72,7 +75,7 @@ There are some built-in capability types supported by the platform. However, the
 |manifest-registry|allows querying the manifest registry|
 
 #### Intent
-If an application intends to interact with functionality of another application, it must declare a respective intent in its manifest. An application has implicit intents for all its own capabilities. Just like for capabilities, it is allowed to use the wildcard character (*) as qualifier value or as qualifier key.
+If an application intends to interact with functionality of another application, it must declare a respective intent in its manifest. An application has implicit intents for all its own capabilities. It is allowed to use the wildcard character (*) as qualifier key and/or wildcard characters (\*) and (?) as qualifier value.
 
 ### Limitation and restrictions
 The platform starts a separate application instance for every entry point invoked. The only exception is when navigating within the same view, but only if the application uses hash-based URLs. The fact of having multiple instances brings some requirements and limitations which you should be aware of:
diff --git a/resources/site/how-to/workbench-application-platform/how-to-install-a-programmatic-intent-handler.md b/resources/site/how-to/workbench-application-platform/how-to-install-a-programmatic-intent-handler.md
index ec0edf6bc..47a8ed473 100644
--- a/resources/site/how-to/workbench-application-platform/how-to-install-a-programmatic-intent-handler.md
+++ b/resources/site/how-to/workbench-application-platform/how-to-install-a-programmatic-intent-handler.md
@@ -7,8 +7,6 @@
 
 The platform allows handling intents in the host application. It is like providing a capability in a sub-application, but in the host application.
 
-The handler can be configured to act as a proxy which the platform invokes only if some application provides a respective capability. For example, `ViewIntentHandler` is a proxy for view capabilities. Upon a view intent, the handler loads capability metadata from the application providing that view, reads the view path and opens the view via the workbench.
-
 Open `app.module.ts` and register the intent handler as multi provider under DI injection `INTENT_HANDLER`
 
 ```typescript
@@ -32,16 +30,15 @@ export class CustomIntentHandler implements IntentHandler {
 
   public readonly type = '...'; ➀
   public readonly qualifier: Qualifier = {...}; ➁
-  public readonly proxy = false; ➂
 
-  public readonly description = '...'; ➃
+  public readonly description = '...'; ➂
 
   public onInit(applicationRegistry: ApplicationRegistry, manifestRegistry: ManifestRegistry): void {
-    ... ➄
+    ... ➃
   }
 
   public onIntent(envelope: MessageEnvelope<IntentMessage>): void {
-    ... ➅
+    ... ➄
   }
 }
 ```
@@ -49,10 +46,9 @@ export class CustomIntentHandler implements IntentHandler {
 |-|-|
 |➀|Sets the type of functionality which this handler provides, e.g. 'auth-token' to reply with the auth-token.|
 |➁|Sets the qualifier which intents must have to be handled. If not set, `NilQualifier` is used.|
-|➂|Indicates if this handler acts as a proxy through which intents are processed, which is `false` by default.|
-|➃|Describes the capability this handler provides.|
-|➄|Optional lifecycle hook that is called after the platform completed registration of applications.<br>Use this method to handle any initialization tasks which require the application or manifest registry.|
-|➅|Method invoked upon the receipt of an intent.|
+|➂|Describes the capability this handler provides.|
+|➃|Optional lifecycle hook that is called after the platform completed registration of applications.<br>Use this method to handle any initialization tasks which require the application or manifest registry.|
+|➄|Method invoked upon the receipt of an intent.|
 
 ## How to issue intents to the programmatic intent handler