Object Flavors
Introduction
Section titled “Introduction”An object flavor is a Secondary Object Type (SOT) that a user can apply to a DMS object in order to enrich it with additional metadata properties. Applying a flavor adds the SOT’s metadata schema to the object without changing its base type. Flavors appear in the object sidebar as applicable actions and as chips on already-enriched objects.
Apps register flavors programmatically during initialization. Flavors can also be provided through a server-side configuration file, making SOTs available in the Shell UI without any client code changes. Both sources feed into a single global registry managed by ShellService.
Why Flavors?
Section titled “Why Flavors?”SOT Enrichment Without Coupling An object gains a new metadata aspect without its base type changing. A plain document can become an invoice, an email, or a quality-controlled asset simply by having the corresponding SOT applied — no structural change to the object itself is required.
Declarative Applicability
Each flavor declares when it is relevant: which base object type it targets (documents, folders, or both) and, for documents, which MIME types it makes sense for. The Shell evaluates these rules automatically and only surfaces flavors that match the current object. An email flavor only appears on .eml files; an image annotation flavor only appears on images.
Multi-Source Extensibility Apps register their own flavors at startup. Tenant-specific SOTs that are not registered by any app can be made available through configuration, with no code changes or redeployment required. Both sources coexist in the same registry.
Controlled User Interaction
Flavors can be configured to open a metadata form before being applied, to apply instantly, or to open a fully custom dialog. Individual flavors can also be locked: preventApply makes a flavor visible but not selectable; preventRemove prevents removal once applied.
The Flavor Data Model
Section titled “The Flavor Data Model”A flavor is defined using the AppFlavor interface, which apps and configuration provide. When the Shell registers a flavor, it creates an ObjectFlavor — a runtime extension of AppFlavor that adds tracking metadata (app, origin, name, description).
// From @yuuvis/client-core — base properties shared by all flavor sourcesinterface ObjectTypeFlavor { id: string; sot: string; icon?: string; svgIcon?: boolean; applicableTo?: { mimeTypes?: string[]; folders?: boolean; documents?: boolean; }; preventApply?: boolean; preventRemove?: boolean;}
// From @yuuvis/client-shell-core — what apps and config defineinterface AppFlavor extends ObjectTypeFlavor { objectTypeID: string; descriptionKey?: string; useDefaultApplyComponent?: boolean; applyComponent?: Type<any>;}
// Runtime type — created by ShellService after registrationtype ObjectFlavor = AppFlavor & { app: string; origin: 'app' | 'config'; name?: string; description?: string;};Property Reference
Section titled “Property Reference”| Property | Type | Interface | Description |
|---|---|---|---|
id | string | ObjectTypeFlavor | Unique identifier for the flavor. Used as the uniqueness key together with app. |
sot | string | ObjectTypeFlavor | The Secondary Object Type ID that is added to the object when the flavor is applied. |
icon | string | ObjectTypeFlavor | Icon name or SVG content. Interpreted as a Material icon name when svgIcon is false, or as SVG when true. |
svgIcon | boolean | ObjectTypeFlavor | If true, the icon value is treated as an SVG identifier rather than a Material icon name. |
applicableTo.mimeTypes | string[] | ObjectTypeFlavor | MIME type patterns. Supports wildcards (image/*, */*). Absent or empty means all MIME types. |
applicableTo.folders | boolean | ObjectTypeFlavor | Whether this flavor can be applied to folders. |
applicableTo.documents | boolean | ObjectTypeFlavor | Whether this flavor can be applied to documents. |
preventApply | boolean | ObjectTypeFlavor | If true, the flavor is shown as applied but users cannot apply it. Useful for read-only SOT display. |
preventRemove | boolean | ObjectTypeFlavor | If true, the flavor cannot be removed once applied. |
objectTypeID | string | AppFlavor | The base object type this flavor targets — typically SystemType.DOCUMENT or SystemType.FOLDER. |
useDefaultApplyComponent | boolean | AppFlavor | Opens the default metadata form overlay on apply. If false and no applyComponent is set, applies instantly. |
applyComponent | Type<any> | AppFlavor | Custom Angular component opened as a dialog on apply. Must extend AbstractApplyObjectFlavorComponent. |
descriptionKey | string | AppFlavor | Translation key for the flavor description shown in the picker dialog. |
app | string | ObjectFlavor | ID of the app that registered the flavor. Set to 'global' for config-registered flavors. Added by ShellService. |
origin | 'app' | 'config' | ObjectFlavor | Source of the flavor registration. Added by ShellService. |
name | string | ObjectFlavor | Localized display name, populated at runtime from the translation system. |
description | string | ObjectFlavor | Localized description, populated at runtime. |
How Flavors Work
Section titled “How Flavors Work”The Shell processes flavors through three distinct phases: registration, applicability filtering, and user interaction. Each phase is driven by specific AppFlavor properties and controlled by ShellService.
The Flavor Registry
Section titled “The Flavor Registry”ShellService maintains a single global list of all registered flavors. Every flavor — regardless of whether it was registered by an app or loaded from configuration — is stored in this list as an ObjectFlavor object.
Two properties are added automatically by the Shell on registration:
app— the ID of the app that registered it (or'global'for config-registered flavors)origin— either'app'(registered in code) or'config'(loaded from theyuv:shell:typesDMS object)
Uniqueness is enforced by the combination of id and app. If two different apps each register a flavor with the same id, they coexist independently. If the same app tries to register the same id twice, the second call is silently ignored.
// Two flavors with the same id but from different apps — both coexist:{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' }{ id: 'flavor.email', app: 'global', origin: 'config' }
// Same id, same app — second registration is ignored:{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' } ✓{ id: 'flavor.email', app: 'io.yuuvis.app.mailarchive', origin: 'app' } ✗ ignoredRegistration Sources
Section titled “Registration Sources”App-registered flavors (origin: 'app') are added in code by an app’s extension service during its init() call, before the Shell UI renders:
// In an extension service:this.#shell.exposeObjectFlavor(MY_FLAVOR, 'io.yuuvis.app.myapp');Config-registered flavors (origin: 'config') are loaded at bootstrap from the yuv:shell:types DMS configuration object. No client code is required — the Shell fetches the configuration, converts each entry to an AppFlavor, and registers it under the 'global' app ID. See Configuring Flavors and Features for setup details.
Applicability Filtering
Section titled “Applicability Filtering”The Shell does not show all registered flavors for every object. When an object is displayed, the Shell uses the applicableTo property to determine which flavors are relevant:
Base type — applicableTo.folders and applicableTo.documents control which base object types the flavor targets.
// Only appears on folders:applicableTo: { folders: true, documents: false }
// Only appears on documents:applicableTo: { folders: false, documents: true }
// Appears on both:applicableTo: { folders: true, documents: true }MIME type — For document flavors, applicableTo.mimeTypes is a list of patterns. The object’s actual MIME type must match at least one. Wildcards are supported. If the list is absent or empty, the flavor applies to all documents regardless of file type.
// Only for RFC 822 email files (.eml):applicableTo: { documents: true, mimeTypes: ['message/rfc822'] }
// For any image file:applicableTo: { documents: true, mimeTypes: ['image/*'] }
// For all documents regardless of file type (omit mimeTypes or use */*):applicableTo: { documents: true }MIME type is only checked for documents. A flavor with folders: true and mimeTypes: ['image/*'] appears on all folders and on image documents only.
Application Modes
Section titled “Application Modes”When a user selects a flavor from the picker, the Shell uses the useDefaultApplyComponent and applyComponent properties to decide what happens next:
| Condition | Behavior |
|---|---|
applyComponent is set | Opens a dialog with that custom Angular component |
useDefaultApplyComponent: true | Opens the built-in overlay: object preview on the left, SOT metadata form on the right |
| Neither | Applies the SOT to the object immediately — no dialog shown |
// Opens metadata form before applying — user fills in SOT fields first:{ sot: 'appExample:invoice', useDefaultApplyComponent: true }
// Applies instantly — no user interaction required:{ sot: 'appExample:reviewed', useDefaultApplyComponent: false }
// Opens a fully custom dialog component:{ sot: 'appExample:email', applyComponent: EmailApplyComponent }The instantApply flag in the yuv:shell:types configuration file maps to this directly: instantApply: true sets useDefaultApplyComponent: false, producing an instant apply with no dialog.
Registering Flavors
Section titled “Registering Flavors”Apps register flavors through ShellService during extension initialization. Flavors must be registered before the Shell UI renders — the init() method of an extension service is the right place.
import { inject, Injectable } from '@angular/core';import { AppFlavor, ClientShellExtension, ShellService } from '@yuuvis/client-shell-core';import { SystemType } from '@yuuvis/client-core';
const APP_ID = 'io.yuuvis.app.mailarchive';
const APP_FLAVORS: AppFlavor[] = [ { id: 'io.yuuvis.app.mailarchive.flavor.email', objectTypeID: SystemType.DOCUMENT, sot: 'appSystemmail:email', icon: 'email_svg_content', svgIcon: true, applicableTo: { documents: true, mimeTypes: ['message/rfc822', 'application/vnd.ms-outlook'] }, useDefaultApplyComponent: true }];
@Injectable()export class AppMailArchiveExtension implements ClientShellExtension { #shell = inject(ShellService);
init(): Promise<any> { const appTitle = this.#shell.getApp(APP_ID)?.title ?? APP_ID; this.#shell.exposeObjectFlavors(APP_FLAVORS, appTitle); return Promise.resolve(); }}Registration methods on ShellService:
| Method | Description |
|---|---|
exposeObjectFlavor(flavor, app?, origin?) | Registers a single flavor. app defaults to 'global'; origin defaults to 'app'. |
exposeObjectFlavors(flavors[], app?, origin?) | Registers multiple flavors in one call. |
concealObjectFlavor(flavor, app?) | Removes a single flavor from the registry. |
concealObjectFlavors(flavors[], app?) | Removes multiple flavors from the registry. |
Querying Flavors
Section titled “Querying Flavors”ShellService provides methods to retrieve flavors from the registry, with optional filtering by app or object context.
| Method | Returns | Description |
|---|---|---|
getObjectFlavors(app?) | ObjectFlavor[] | All registered flavors, optionally scoped to one app. |
getApplicableDocumentFlavors(mimeType, app?, customFlavors?) | ObjectFlavor[] | Flavors applicable to a document with the given MIME type. Excludes folder-only flavors and evaluates MIME patterns. |
getApplicableFolderFlavors(app?, customFlavors?) | ObjectFlavor[] | Flavors applicable to folders. Excludes flavors with preventApply: true. |
getAppliedObjectFlavors(dmsObject) | { applied: ObjectFlavor[], applicable: ObjectFlavor[] } | For a specific object: which registered flavors are already applied (SOT present) and which can still be applied. |
The customFlavors parameter on the document and folder methods allows passing additional flavors that are managed by an app internally without being exposed globally to the registry.
Applying and Removing Flavors
Section titled “Applying and Removing Flavors”ShellService exposes dedicated methods for applying and removing flavors. Each method enforces the preventApply and preventRemove constraints defined on the flavor before making any changes to the DMS object.
Triggering an Apply Interaction
Section titled “Triggering an Apply Interaction”triggerApplyObjectFlavor() is the main entry point for user-initiated flavor application. It handles the dialog/instant split automatically:
import { inject } from '@angular/core';import { ShellService } from '@yuuvis/client-shell-core';
export class MyComponent { #shell = inject(ShellService);
applyFlavor(dmsObject: DmsObject, flavor: ObjectFlavor) { this.#shell .triggerApplyObjectFlavor(dmsObject, flavor) .subscribe(success => { if (success) { // flavor was applied or dialog was opened } }); }}- If
flavor.preventApplyistrue, returnsfalseimmediately - If an
applyComponentis set (oruseDefaultApplyComponentistrue), opens the dialog component - Otherwise applies the flavor directly by updating the DMS object
Applying Directly
Section titled “Applying Directly”applyObjectFlavor() applies the flavor to the object and updates the DMS record immediately, with no dialog. It is typically called from within an apply component after the user has filled in the metadata form:
this.#shell.applyObjectFlavor(dmsObject, flavor, formData).subscribe();The method adds flavor.sot to the object’s SECONDARY_OBJECT_TYPE_IDS array, merging any additional formData into the update.
Removing a Flavor
Section titled “Removing a Flavor”this.#shell.removeObjectFlavor(dmsObject, flavor).subscribe();If flavor.preventRemove is true, the call returns false without making any changes. Otherwise, the SOT is removed from the object’s SECONDARY_OBJECT_TYPE_IDS and the DMS record is updated.
Custom Apply Components
Section titled “Custom Apply Components”When the default metadata form overlay is not suitable, a flavor can define its own apply component. This is useful when applying the flavor requires more complex user interaction — for example, mapping file metadata to SOT fields automatically, or showing a multi-step wizard.
Custom apply components extend AbstractApplyObjectFlavorComponent from @yuuvis/client-framework:
import { Component, inject } from '@angular/core';import { AbstractApplyObjectFlavorComponent} from '@yuuvis/client-framework';import { ShellService } from '@yuuvis/client-shell-core';
@Component({ selector: 'app-my-apply-flavor', standalone: true, template: ` <h2>Apply {{ flavor().id }}</h2> <!-- custom form here --> <button (click)="apply()">Apply</button> <button (click)="dialogRef.close()">Cancel</button> `})export class MyApplyFlavorComponent extends AbstractApplyObjectFlavorComponent { #shell = inject(ShellService);
apply() { const data = { /* collect form values */ }; this.#shell .applyObjectFlavor(this.dmsObject(), this.flavor(), data) .subscribe(() => this.dialogRef.close()); }}The base class provides:
dmsObject()— the object to apply the flavor toflavor()— the flavor being applieddata()— optional contextual data passed by the callerdialogRef— theMatDialogReffor closing the dialog
Register the component on the flavor definition:
const MY_FLAVOR: AppFlavor = { id: 'com.example.flavor.custom', sot: 'appExample:customType', objectTypeID: SystemType.DOCUMENT, applyComponent: MyApplyFlavorComponent, // ...};UI Components Overview
Section titled “UI Components Overview”The following components from @yuuvis/client-framework render flavors in the Shell UI. They consume the ShellService flavor API and can be reused in custom views.
| Component | Selector | Description |
|---|---|---|
ObjectFlavorComponent | yuv-object-flavor | Displays applied flavors as chips and an “Add” button for applicable ones. Used in the object sidebar. |
ObjectFlavorPickerComponent | (dialog) | Modal dialog listing all applicable flavors for the current object. Opens when the user clicks “Add”. |
DefaultApplyFlavorComponent | (dialog) | Default apply overlay: object preview on the left, SOT metadata form on the right. |
FlavorChipComponent | yuv-flavor-chip | Renders a single flavor as a chip with icon, name, and optional remove button. |
Use ObjectFlavorComponent in custom object detail views by passing the current DMS object:
<yuv-object-flavor [dmsObject]="myObject" />The component determines applicable and applied flavors automatically and handles the full apply/remove interaction.