Repository: MetaMorphic-Digital/draw-steel
Pull request #449: Simple embed implementation
Author: login: JPMeehan
Labels: name: data model
description: Document and Data Model issues
Created: 2025-05-07T02:28:16Z | Merged: 2025-05-09T17:17:37Z

Added a basic implementation of toEmbed for active effects


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44bebb115..73572c2cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,6 +46,7 @@
 - Added a prompt at the end of combat to award victories to each character in the combat. (#388)
 - Added ways to spend hero tokens from the character sheet (#54)
 - Added a number of generic icons for different monster roles under `systems/draw-steel/assets/roles`.
+- Added basic embed functionality for active effects.
 
 ### Changed
 - Changed system minimum to v13.339, verified system for v13 generally.
diff --git a/src/module/applications/sheets/actor-sheet.mjs b/src/module/applications/sheets/actor-sheet.mjs
index cdeda0270..17a445626 100644
--- a/src/module/applications/sheets/actor-sheet.mjs
+++ b/src/module/applications/sheets/actor-sheet.mjs
@@ -2,6 +2,7 @@ import { AbilityModel, FeatureModel } from "../../data/item/_module.mjs";
 import { DrawSteelChatMessage, DrawSteelItem } from "../../documents/_module.mjs";
 import DrawSteelItemSheet from "./item-sheet.mjs";
 import DSDocumentSheetMixin from "../api/document-sheet-mixin.mjs";
+import enrichHTML from "../../utils/enrichHTML.mjs";
 
 /** @import { FormSelectOption } from "@client/applications/forms/fields.mjs" */
 /** @import { ActorSheetItemContext, ActorSheetAbilitiesContext } from "./_types.js" */
@@ -115,7 +116,6 @@ export default class DrawSteelActorSheet extends DSDocumentSheetMixin(sheets.Act
   /** @inheritdoc */
   async _preparePartContext(partId, context, options) {
     await super._preparePartContext(partId, context, options);
-    const TextEditor = foundry.applications.ux.TextEditor.implementation;
     switch (partId) {
       case "stats":
         context.characteristics = this._getCharacteristics();
@@ -132,22 +132,8 @@ export default class DrawSteelActorSheet extends DSDocumentSheetMixin(sheets.Act
         break;
       case "biography":
         context.languages = this._getLanguages();
-        context.enrichedBiography = await TextEditor.enrichHTML(
-          this.actor.system.biography.value,
-          {
-            secrets: this.document.isOwner,
-            rollData: this.actor.getRollData(),
-            relativeTo: this.actor,
-          },
-        );
-        context.enrichedGMNotes = await TextEditor.enrichHTML(
-          this.actor.system.biography.gm,
-          {
-            secrets: this.document.isOwner,
-            rollData: this.actor.getRollData(),
-            relativeTo: this.actor,
-          },
-        );
+        context.enrichedBiography = await enrichHTML(this.actor.system.biography.value, { relativeTo: this.actor });
+        context.enrichedGMNotes = await enrichHTML(this.actor.system.biography.gm, { relativeTo: this.actor });
         break;
       case "effects":
         context.effects = this.prepareActiveEffectCategories();
diff --git a/src/module/applications/sheets/item-sheet.mjs b/src/module/applications/sheets/item-sheet.mjs
index 667549ed2..6f9535980 100644
--- a/src/module/applications/sheets/item-sheet.mjs
+++ b/src/module/applications/sheets/item-sheet.mjs
@@ -1,4 +1,5 @@
 import { systemPath } from "../../constants.mjs";
+import enrichHTML from "../../utils/enrichHTML.mjs";
 import DSDocumentSheetMixin from "../api/document-sheet-mixin.mjs";
 
 const { sheets, ux } = foundry.applications;
@@ -124,22 +125,8 @@ export default class DrawSteelItemSheet extends DSDocumentSheetMixin(sheets.Item
 
     switch (partId) {
       case "description":
-        context.enrichedDescription = await ux.TextEditor.implementation.enrichHTML(
-          this.item.system.description.value,
-          {
-            secrets: this.document.isOwner,
-            rollData: this.item.getRollData(),
-            relativeTo: this.item,
-          },
-        );
-        context.enrichedGMNotes = await ux.TextEditor.implementation.enrichHTML(
-          this.item.system.description.gm,
-          {
-            secrets: this.document.isOwner,
-            rollData: this.item.getRollData(),
-            relativeTo: this.item,
-          },
-        );
+        context.enrichedDescription = await enrichHTML(this.item.system.description.value, { relativeTo: this.item });
+        context.enrichedGMNotes = await enrichHTML(this.item.system.description.gm, { relativeTo: this.item });
         break;
       case "details":
         context.detailsPartial = this.item.system.constructor.metadata.detailsPartial ?? null;
@@ -533,7 +520,7 @@ export default class DrawSteelItemSheet extends DSDocumentSheetMixin(sheets.Item
    * @protected
    */
   async _onDrop(event) {
-    const data = TextEditor.getDragEventData(event);
+    const data = ux.TextEditor.implementation.getDragEventData(event);
     const item = this.item;
     const allowed = Hooks.call("dropItemSheetData", item, this, data);
     if (allowed === false) return;
diff --git a/src/module/applications/sidebar/tabs/combat-tracker.mjs b/src/module/applications/sidebar/tabs/combat-tracker.mjs
index b44202af9..c68857ce0 100644
--- a/src/module/applications/sidebar/tabs/combat-tracker.mjs
+++ b/src/module/applications/sidebar/tabs/combat-tracker.mjs
@@ -4,10 +4,12 @@ import { DrawSteelCombatant, DrawSteelCombatantGroup } from "../../../documents/
 /** @import { ContextMenuEntry } from "@client/applications/ux/context-menu.mjs" */
 /** @import DrawSteelActor from "../../../documents/actor.mjs" */
 
+const { ux, sidebar } = foundry.applications;
+
 /**
  * A custom combat tracker that supports Draw Steel's initiative system
  */
-export default class DrawSteelCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
+export default class DrawSteelCombatTracker extends sidebar.tabs.CombatTracker {
   /** @inheritdoc */
   static DEFAULT_OPTIONS = {
     actions: {
@@ -185,7 +187,7 @@ export default class DrawSteelCombatTracker extends foundry.applications.sidebar
     await super._onRender(context, options);
 
     if (game.settings.get(systemID, "initiativeMode") !== "default") return;
-    new foundry.applications.ux.DragDrop.implementation({
+    new ux.DragDrop.implementation({
       dragSelector: ".combatant",
       dropSelector: ".combatant-group, .combat-tracker",
       permissions: {
@@ -233,7 +235,7 @@ export default class DrawSteelCombatTracker extends foundry.applications.sidebar
   async _onDrop(event) {
     // Combat Tracker contains combatant groups, which means this would fire twice
     event.stopPropagation();
-    const data = TextEditor.getDragEventData(event);
+    const data = ux.TextEditor.implementation.getDragEventData(event);
     /** @type {DrawSteelCombatant} */
     const combatant = await DrawSteelCombatant.fromDropData(data);
     /** @type {HTMLLIElement | null} */
diff --git a/src/module/data/effect/base.mjs b/src/module/data/effect/base.mjs
index fa6fc9fb4..98914daba 100644
--- a/src/module/data/effect/base.mjs
+++ b/src/module/data/effect/base.mjs
@@ -1,3 +1,4 @@
+import enrichHTML from "../../utils/enrichHTML.mjs";
 import FormulaField from "../fields/formula-field.mjs";
 
 /**
@@ -60,4 +61,17 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    const enriched = await enrichHTML(this.description.value, { ...options, relativeTo: this.parent });
+
+    const embed = document.createElement("div");
+    embed.classList.add("draw-steel", this.parent.type);
+    embed.innerHTML = enriched;
+
+    return embed;
+  }
+
 }
diff --git a/src/module/data/item/base.mjs b/src/module/data/item/base.mjs
index 0a93cffe1..4eb32240a 100644
--- a/src/module/data/item/base.mjs
+++ b/src/module/data/item/base.mjs
@@ -1,3 +1,4 @@
+import enrichHTML from "../../utils/enrichHTML.mjs";
 import SourceModel from "../models/source.mjs";
 import SubtypeModelMixin from "../subtype-model-mixin.mjs";
 
@@ -78,9 +79,7 @@ export default class BaseItemModel extends SubtypeModelMixin(foundry.abstract.Ty
 
   /** @inheritdoc */
   async toEmbed(config, options = {}) {
-
-    options.rollData ??= this.parent.getRollData();
-    const enriched = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.description.value, options);
+    const enriched = await enrichHTML(this.description.value, { ...options, relativeTo: this.parent });
 
     const embed = document.createElement("div");
     embed.classList.add("draw-steel", this.parent.type);
diff --git a/src/module/data/item/kit.mjs b/src/module/data/item/kit.mjs
index 809453672..941240b8f 100644
--- a/src/module/data/item/kit.mjs
+++ b/src/module/data/item/kit.mjs
@@ -1,4 +1,5 @@
 import { systemPath } from "../../constants.mjs";
+import enrichHTML from "../../utils/enrichHTML.mjs";
 import { setOptions } from "../helpers.mjs";
 import BaseItemModel from "./base.mjs";
 
@@ -102,14 +103,7 @@ export default class KitModel extends BaseItemModel {
       config: ds.CONFIG,
       showDescription: true, // used to prevent showing the description on the details tab of the kit sheet
     };
-    context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
-      this.description.value,
-      {
-        secrets: this.parent.isOwner,
-        rollData: this.parent.getRollData(),
-        relativeTo: this.parent,
-      },
-    );
+    context.enrichedDescription = await enrichHTML(this.description.value, { ...options, relativeTo: this.parent });
     await this.getSheetContext(context);
     //TODO: Once kits provide a signature item, add the ability embed or link to the item
     const kitBody = await foundry.applications.handlebars.renderTemplate(systemPath("templates/item/embeds/kit.hbs"), context);
diff --git a/src/module/data/item/project.mjs b/src/module/data/item/project.mjs
index a0b66a062..ad9d51e26 100644
--- a/src/module/data/item/project.mjs
+++ b/src/module/data/item/project.mjs
@@ -118,7 +118,10 @@ export default class ProjectModel extends BaseItemModel {
     const characteristicList = Array.from(this.rollCharacteristic).map(c => ds.CONFIG.characteristics[c]?.label ?? c);
     context.formattedCharacteristics = characteristicFormatter.format(characteristicList);
 
-    if (this.yield.item) context.itemLink = await foundry.applications.ux.TextEditor.implementation.enrichHTML(`@UUID[${this.yield.item}]`);
+    if (this.yield.item) {
+      const item = await fromUuid(this.yield.item);
+      context.itemLink = item?.toAnchor().outerHTML;
+    }
   }
 
   /**
diff --git a/src/module/documents/_types.d.ts b/src/module/documents/_types.d.ts
index a7a67b5b3..bd00d9720 100644
--- a/src/module/documents/_types.d.ts
+++ b/src/module/documents/_types.d.ts
@@ -37,8 +37,7 @@ declare module "@client/documents/_module.mjs" {
   interface Item < Model extends ItemModel = ItemModel > extends ItemData {
     type: Model["metadata"]["type"];
     system: InstanceType < Model > ;
-    effects: Collection < string,
-    DrawSteelActiveEffect > ;
+    effects: Collection < string, DrawSteelActiveEffect > ;
   }
 
   interface ActiveEffect extends ActiveEffectData {
diff --git a/src/module/documents/active-effect.mjs b/src/module/documents/active-effect.mjs
index 56a0452d9..4eac52b01 100644
--- a/src/module/documents/active-effect.mjs
+++ b/src/module/documents/active-effect.mjs
@@ -122,4 +122,28 @@ export default class DrawSteelActiveEffect extends foundry.documents.ActiveEffec
 
     super._applyOverride(actor, change, current, delta, changes);
   }
+
+  /**
+   * Return a data object which defines the data schema against which dice rolls can be evaluated.
+   * Potentially usable in the future. May also want to adjust details to care about
+   * @returns {object}
+   */
+  getRollData() {
+    // Will naturally have actor data at the base & `item` for any relevant item data
+    const rollData = this.parent?.getRollData() ?? {};
+
+    // Shallow copy
+    rollData.effect = { ...this.system, duration: this.duration, flags: this.flags, name: this.name };
+
+    // Statuses provided by *this* active effect
+    for (const status of this.statuses) {
+      rollData.effect.statuses[status] = 1;
+    }
+
+    if (this.system.modifyRollData instanceof Function) {
+      this.system.modifyRollData(rollData);
+    }
+
+    return rollData;
+  }
 }
diff --git a/src/module/documents/actor.mjs b/src/module/documents/actor.mjs
index 81d8bc623..068ddfc90 100644
--- a/src/module/documents/actor.mjs
+++ b/src/module/documents/actor.mjs
@@ -1,7 +1,11 @@
 import BaseDocumentMixin from "./base-document-mixin.mjs";
 
+// Necessary for continued type functionality with mixin
+/** @import Actor from "@client/documents/actor.mjs"; */
+
 /**
  * A document subclass adding system-specific behavior and registered in CONFIG.Actor.documentClass
+ * @extends Actor
  */
 export default class DrawSteelActor extends BaseDocumentMixin(foundry.documents.Actor) {
   /**
diff --git a/src/module/documents/base-document-mixin.mjs b/src/module/documents/base-document-mixin.mjs
index 96f49736d..1f978b0bd 100644
--- a/src/module/documents/base-document-mixin.mjs
+++ b/src/module/documents/base-document-mixin.mjs
@@ -1,6 +1,7 @@
 /**
  * Mixin for common functions used across most or all document classes in this system.
  * @mixin
+ *
  */
 export default base => {
   return class DrawSteelDocument extends base {
diff --git a/src/module/documents/item.mjs b/src/module/documents/item.mjs
index 5d4604124..1c3a98397 100644
--- a/src/module/documents/item.mjs
+++ b/src/module/documents/item.mjs
@@ -1,7 +1,11 @@
 import BaseDocumentMixin from "./base-document-mixin.mjs";
 
+// Necessary for continued type functionality with mixin
+/** @import Item from "@client/documents/item.mjs"; */
+
 /**
  * A document subclass adding system-specific behavior and registered in CONFIG.Item.documentClass
+ * @extends Item
  */
 export default class DrawSteelItem extends BaseDocumentMixin(foundry.documents.Item) {
   /** @inheritdoc */
diff --git a/src/module/utils/_module.mjs b/src/module/utils/_module.mjs
index f5d2c2e89..28f38f1e9 100644
--- a/src/module/utils/_module.mjs
+++ b/src/module/utils/_module.mjs
@@ -1,2 +1,3 @@
 export { default as constructHTMLButton } from "./construct-html-button.mjs";
+export { default as enrichHTML } from "./enrichHTML.mjs";
 export { default as selectedActors } from "./selected-actors.mjs";
diff --git a/src/module/utils/enrichHTML.mjs b/src/module/utils/enrichHTML.mjs
new file mode 100644
index 000000000..29c65c381
--- /dev/null
+++ b/src/module/utils/enrichHTML.mjs
@@ -0,0 +1,18 @@
+/** @import {EnrichmentOptions} from "@client/applications/ux/text-editor.mjs" */
+
+/**
+ * Helper function that reduces path length for enrichment calls and improves default handling.
+ * Enrich HTML content by replacing or augmenting components of it
+ * @param {string} content                  The original HTML content (as a string)
+ * @param {EnrichmentOptions} [options={}]  Additional options which configure how HTML is enriched
+ * @returns {Promise<string>}               The enriched HTML content
+ */
+export default async function enrichHTML(content, options = {}) {
+  // Override document-related options with the relative document's info
+  if (options.relativeTo) {
+    // allow secrets=false to prevent secret display
+    options.secrets &&= options.relativeTo.isOwner;
+    if (options.relativeTo.getRollData instanceof Function) options.rollData = options.relativeTo.getRollData();
+  }
+  return foundry.applications.ux.TextEditor.implementation.enrichHTML(content, options);
+}


Reviews:
user: login: krbz999
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T18:54:46Z
user: login: JPMeehan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T23:31:22Z
user: login: JPMeehan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T23:51:39Z
user: login: krbz999
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T10:47:07Z
user: login: JPMeehan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T13:52:02Z
user: login: krbz999
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T16:37:10Z

Review comments:
diff_hunk: @@ -60,4 +60,19 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    // TODO: Figure out what roll data is "supposed" to be for effects
path: src/module/data/effect/base.mjs
user: login: krbz999
body: If we can figure this out, then I'll be happy with it. My suggestion would be to implement `ActiveEffect#getRollData` which would just return `{ ...this.system }`.
created_at: 2025-05-08T18:53:46Z
author_association: MEMBER
diff_hunk: @@ -60,4 +60,19 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    // TODO: Figure out what roll data is "supposed" to be for effects
+
+    const enriched = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.parent.description, options);
path: src/module/data/effect/base.mjs
user: login: krbz999
body: Dunno about you but these long paths are starting to wear on me, we should implement a util function.
created_at: 2025-05-08T18:54:24Z
author_association: MEMBER
diff_hunk: @@ -60,4 +60,19 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    // TODO: Figure out what roll data is "supposed" to be for effects
path: src/module/data/effect/base.mjs
user: login: JPMeehan
body: Put together a more comprehensive function. I expect in the future we'll want to figure out if we also need to expand that for applied effects but it's a pretty good start.
created_at: 2025-05-08T23:31:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,4 +60,19 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    // TODO: Figure out what roll data is "supposed" to be for effects
+
+    const enriched = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.parent.description, options);
path: src/module/data/effect/base.mjs
user: login: JPMeehan
body: So, I'm working up a possible implementation, but in places where it's _not_ a one-off like our ActorSheet, I use `const TextEditor = foundry.applications.ux.TextEditor.implementation;`. I agree the long path is really annoying and I wish core had considered that when they made these adjustments.
created_at: 2025-05-08T23:51:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,14 +103,13 @@ export default class KitModel extends BaseItemModel {
       config: ds.CONFIG,
       showDescription: true, // used to prevent showing the description on the details tab of the kit sheet
     };
-    context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
-      this.description.value,
-      {
-        secrets: this.parent.isOwner,
-        rollData: this.parent.getRollData(),
-        relativeTo: this.parent,
-      },
-    );
+    // Override document-related options with this document's info so it's consistent wherever embedded
+    context.enrichedDescription = await enrichHTML(this.description.value, {
+      ...options,
+      relativeTo: this.parent,
+      rollData: this.parent.getRollData(),
+      secrets: this.parent.isOwner,
path: src/module/data/item/kit.mjs
user: login: krbz999
body: rollData and secrets are done in the helper method
created_at: 2025-05-09T10:45:12Z
author_association: MEMBER
diff_hunk: @@ -78,9 +79,13 @@ export default class BaseItemModel extends SubtypeModelMixin(foundry.abstract.Ty
 
   /** @inheritdoc */
   async toEmbed(config, options = {}) {
-
-    options.rollData ??= this.parent.getRollData();
-    const enriched = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.description.value, options);
+    // Override document-related options with this document's info so it's consistent wherever embedded
+    const enriched = await enrichHTML(this.description.value, {
+      ...options,
+      relativeTo: this.parent,
+      rollData: this.parent.getRollData(),
+      secrets: this.parent.isOwner,
path: src/module/data/item/base.mjs
user: login: krbz999
body: Roll data and secrets are done in the helper method. imo we dont have a reason to enforce and override what is passed into `options`
created_at: 2025-05-09T10:46:23Z
author_association: MEMBER
diff_hunk: @@ -60,4 +61,23 @@ export default class BaseEffectModel extends foundry.abstract.TypeDataModel {
       label: this.durationLabel,
     };
   }
+
+  /** @inheritdoc */
+  async toEmbed(config, options = {}) {
+
+    // Override document-related options with this document's info so it's consistent wherever embedded
+    const enriched = await enrichHTML(this.description.value, {
+      ...options,
+      relativeTo: this.parent,
+      rollData: this.parent.getRollData(),
+      secrets: this.parent.isOwner,
path: src/module/data/effect/base.mjs
user: login: krbz999
body: same here
created_at: 2025-05-09T10:46:33Z
author_association: MEMBER
diff_hunk: @@ -102,14 +103,13 @@ export default class KitModel extends BaseItemModel {
       config: ds.CONFIG,
       showDescription: true, // used to prevent showing the description on the details tab of the kit sheet
     };
-    context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
-      this.description.value,
-      {
-        secrets: this.parent.isOwner,
-        rollData: this.parent.getRollData(),
-        relativeTo: this.parent,
-      },
-    );
+    // Override document-related options with this document's info so it's consistent wherever embedded
+    context.enrichedDescription = await enrichHTML(this.description.value, {
+      ...options,
+      relativeTo: this.parent,
+      rollData: this.parent.getRollData(),
+      secrets: this.parent.isOwner,
path: src/module/data/item/kit.mjs
user: login: JPMeehan
body: I'm only null coalescing there, while this is is a hard set. Should I change the helper to hard override?

The basic case I'm envisioning is that you embed say an item in some other text block that is getting roll data — we want to preserve that items roll data context. Furthermore, if you embed the description of an item you don't own, it shouldn't display that items secrets.
created_at: 2025-05-09T13:52:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -102,14 +103,13 @@ export default class KitModel extends BaseItemModel {
       config: ds.CONFIG,
       showDescription: true, // used to prevent showing the description on the details tab of the kit sheet
     };
-    context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
-      this.description.value,
-      {
-        secrets: this.parent.isOwner,
-        rollData: this.parent.getRollData(),
-        relativeTo: this.parent,
-      },
-    );
+    // Override document-related options with this document's info so it's consistent wherever embedded
+    context.enrichedDescription = await enrichHTML(this.description.value, {
+      ...options,
+      relativeTo: this.parent,
+      rollData: this.parent.getRollData(),
+      secrets: this.parent.isOwner,
path: src/module/data/item/kit.mjs
user: login: krbz999
body: Yeah that sounds good then.
created_at: 2025-05-09T16:37:10Z
author_association: MEMBER

PR comments:
user: login: krbz999
created_at: 2025-05-07T10:03:14Z
body: But why do we need this?
author_association: MEMBER
user: login: JPMeehan
created_at: 2025-05-07T15:52:34Z
body: > But why do we need this?

Provides a basic & easy fallback since we otherwise have ownership of the system models.
author_association: CONTRIBUTOR
user: login: krbz999
created_at: 2025-05-07T16:35:18Z
body: > > But why do we need this?
> 
> Provides a basic & easy fallback since we otherwise have ownership of the system models.

My question is rather, what are we implementing right now that is requiring this? I don't think we should add new features just for the sake of adding features, they have to serve a purpose.
author_association: MEMBER
user: login: JPMeehan
created_at: 2025-05-08T16:20:26Z
body: > > > But why do we need this?
> > 
> > 
> > Provides a basic & easy fallback since we otherwise have ownership of the system models.
> 
> My question is rather, what are we implementing right now that is requiring this? I don't think we should add new features just for the sake of adding features, they have to serve a purpose.

I guess there's nothing _right now_, I was mostly prompted by some discussion of expanding the range of core embeds. In terms of practical use, I could see minimizing duplicate text by using `@Embed[.ActiveEffect.relativeId]` to grab text from a child AE in an item.
author_association: CONTRIBUTOR
user: login: krbz999
created_at: 2025-05-08T18:52:31Z
body: 
> I guess there's nothing _right now_, I was mostly prompted by some discussion of expanding the range of core embeds. In terms of practical use, I could see minimizing duplicate text by using `@Embed[.ActiveEffect.relativeId]` to grab text from a child AE in an item.

That's a good use case, though not one we need right now - but perhaps soon? This isn't a very large PR so I don't feel strongly, but I'm worried about bloat if implementing features for the sake of features. 
author_association: MEMBER