From 54ce6fb658576ba1bcfe374ae06ecd1e9b854f3f Mon Sep 17 00:00:00 2001 From: Luis Rosas Date: Mon, 9 Feb 2026 10:06:52 -0700 Subject: [PATCH 1/7] feat(task): add baseMixin support in TaskTypes - Add baseMixin field to TaskType interface (plugins/task/src/index.ts) - Add @Prop decorator for baseMixin in model (models/task/src/index.ts) - Modify mixin creation logic to use baseMixin when provided (plugins/task/src/utils.ts) - Add validateMixinHierarchy function for inheritance validation - Create MixinSelector.svelte component for UI selection - Add i18n strings for BaseMixin, BaseMixinDescription, NoBaseMixin This allows TaskTypes to inherit attributes from existing mixins while maintaining backwards compatibility. Signed-off-by: Luis Rosas --- models/task/src/index.ts | 3 + plugins/task-assets/lang/en.json | 3 + .../components/taskTypes/MixinSelector.svelte | 82 +++++++++++++++++++ plugins/task-resources/src/plugin.ts | 5 +- plugins/task/src/index.ts | 3 + plugins/task/src/utils.ts | 20 ++++- 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 plugins/task-resources/src/components/taskTypes/MixinSelector.svelte diff --git a/models/task/src/index.ts b/models/task/src/index.ts index 288c95d57ff..266f1e1f43c 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -210,6 +210,9 @@ export class TTaskType extends TDoc implements TaskType { @Prop(ArrOf(TypeRef(task.class.TaskType)), getEmbeddedLabel('Parent')) allowedAsChildOf!: Ref[] // In case of specified, task type is for sub-tasks + @Prop(TypeRef(core.class.Mixin), getEmbeddedLabel('Base Mixin')) + baseMixin?: Ref> // Existing mixin to inherit attributes from + @Prop(TypeRef(core.class.Class), getEmbeddedLabel('Task class')) ofClass!: Ref> // Base class for task diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index 3c9edeb61cb..ea10c4b4a78 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -79,6 +79,9 @@ "StatusChange": "Status changed", "TaskCreated": "Task created", "TaskType": "Task type", + "BaseMixin": "Base Mixin", + "BaseMixinDescription": "Inherit attributes from an existing mixin", + "NoBaseMixin": "None (create new)", "ManageProjects": "Project types", "Export": "Export", "CreateProjectType": "Create project type", diff --git a/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte b/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte new file mode 100644 index 00000000000..4be3c0ca0a2 --- /dev/null +++ b/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte @@ -0,0 +1,82 @@ + + +
+
+ + diff --git a/plugins/task-resources/src/plugin.ts b/plugins/task-resources/src/plugin.ts index 88ef8abd842..ab8be834d26 100644 --- a/plugins/task-resources/src/plugin.ts +++ b/plugins/task-resources/src/plugin.ts @@ -89,7 +89,10 @@ export default mergeIds(taskId, task, { RenameStatus: '' as IntlString, UpdateTasksStatusRequest: '' as IntlString, TaskTypes: '' as IntlString, - Collections: '' as IntlString + Collections: '' as IntlString, + BaseMixin: '' as IntlString, + BaseMixinDescription: '' as IntlString, + NoBaseMixin: '' as IntlString }, status: { AssigneeRequired: '' as IntlString diff --git a/plugins/task/src/index.ts b/plugins/task/src/index.ts index 412161345ce..547bc2b4e73 100644 --- a/plugins/task/src/index.ts +++ b/plugins/task/src/index.ts @@ -120,6 +120,9 @@ export interface TaskType extends Doc, IconProps { // Specify if task is allowed to be used as subtask of following tasks. allowedAsChildOf?: Ref[] + // Existing mixin to inherit attributes from (optional) + baseMixin?: Ref> + ofClass: Ref> // Base class for task targetClass: Ref> // Class or Mixin mixin to hold all user defined attributes. diff --git a/plugins/task/src/utils.ts b/plugins/task/src/utils.ts index 69f43946385..f88cf3a83d3 100644 --- a/plugins/task/src/utils.ts +++ b/plugins/task/src/utils.ts @@ -21,6 +21,7 @@ import core, { DocumentQuery, Hierarchy, IdMap, + Mixin, Ref, Status, TxOperations, @@ -330,11 +331,14 @@ async function createTaskTypes ( const targetClassId = `${taskId}:type:mixin` as Ref> tdata.targetClass = targetClassId + // Use baseMixin if provided, otherwise extend ofClass directly + const extendsClass = data.baseMixin ?? data.ofClass + await client.createDoc( core.class.Mixin, core.space.Model, { - extends: data.ofClass, + extends: extendsClass, kind: ClassifierKind.MIXIN, label: ofClassClass.label, icon: ofClassClass.icon @@ -353,3 +357,17 @@ async function createTaskTypes ( } return hasUpdates } + +/** + * @public + * Validates that a baseMixin is compatible with the ofClass hierarchy. + * Returns true if baseMixin is undefined or if it derives from ofClass. + */ +export function validateMixinHierarchy ( + hierarchy: Hierarchy, + baseMixin: Ref> | undefined, + ofClass: Ref> +): boolean { + if (baseMixin === undefined) return true + return hierarchy.isDerived(baseMixin, ofClass) +} From c5450c4fa7fee43c8c0cd060cb5de52185ba0814 Mon Sep 17 00:00:00 2001 From: Luis Rosas Date: Mon, 9 Feb 2026 11:09:19 -0700 Subject: [PATCH 2/7] feat(task): integrate MixinSelector into CreateTaskType UI Signed-off-by: Luis Rosas --- .../components/taskTypes/CreateTaskType.svelte | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte b/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte index 9d7dfa4844c..dd5d2aac72e 100644 --- a/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte +++ b/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte @@ -28,6 +28,7 @@ import { DropdownIntlItem, Modal, ModernEditbox, Label, ButtonMenu } from '@hcengineering/ui' import task from '../../plugin' import TaskTypeKindEditor from './TaskTypeKindEditor.svelte' + import MixinSelector from './MixinSelector.svelte' import { clearSettingsStore } from '@hcengineering/setting-resources' const client = getClient() @@ -68,6 +69,8 @@ let { kind, name, targetClass, statusCategories, statuses, allowedAsChildOf } = taskType !== undefined ? { ...taskType } : { ...defaultTaskType(type) } + let baseMixin = taskType?.baseMixin + function findStatusClass (_class: Ref>): Ref> | undefined { const h = getClient().getHierarchy() const attrs = h.getAllAttributes(_class) @@ -98,7 +101,8 @@ allowedAsChildOf, statusClass: findStatusClass(ofClass) ?? core.class.Status, parent: type._id, - icon: descr.icon + icon: descr.icon, + baseMixin } if (taskType === undefined && descr.statusCategoriesFunc !== undefined) { @@ -136,8 +140,10 @@ } else { const ofClassClass = client.getHierarchy().getClass(ofClass) // Create target class for custom field. + // Use baseMixin if provided, otherwise use ofClass + const extendsClass = baseMixin ?? ofClass _taskType.targetClass = await client.createDoc(core.class.Class, core.space.Model, { - extends: ofClass, + extends: extendsClass, kind: ClassifierKind.MIXIN, label: getEmbeddedLabel(name), icon: ofClassClass.icon @@ -181,6 +187,12 @@ +
+ +
{#if taskTypeDescriptors.length > 1}
From 6fa4c2ccd7574b2971722fe70b1f3ad8985ba4a8 Mon Sep 17 00:00:00 2001 From: Luis Rosas Date: Mon, 9 Feb 2026 11:21:38 -0700 Subject: [PATCH 3/7] feat(task): query existing TaskTypes for inheritance selector Signed-off-by: Luis Rosas --- plugins/task-assets/lang/en.json | 4 +- .../components/taskTypes/MixinSelector.svelte | 63 +++++++++++-------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index ea10c4b4a78..c5c4ea53682 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -79,8 +79,8 @@ "StatusChange": "Status changed", "TaskCreated": "Task created", "TaskType": "Task type", - "BaseMixin": "Base Mixin", - "BaseMixinDescription": "Inherit attributes from an existing mixin", + "BaseMixin": "Inherit from", + "BaseMixinDescription": "Inherit attributes from an existing task type", "NoBaseMixin": "None (create new)", "ManageProjects": "Project types", "Export": "Export", diff --git a/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte b/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte index 4be3c0ca0a2..9532bd9bb98 100644 --- a/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte +++ b/plugins/task-resources/src/components/taskTypes/MixinSelector.svelte @@ -1,9 +1,9 @@
-