@skaile/forge-editor-compose 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 - 2026-11-05
4
+
5
+ ### Added
6
+
7
+ - `composeBlockTypes({ slotChrome: 'template' })`: the slot of a template document as a neutral gray card (`TEMPLATE_SLOT_TONE`) with a read-only field row under the title (identifier, `je`, `optional` when `ja`, `Auswahl`, and `KI: <first line of anweisung>`) and a body that folds per block or all at once. The default `'plain'` keeps today's slot for case and instance documents. `slotFieldLabels` replaces the German prefixes. The template look needs `@skaile/forge-editor` 6.7.0 (`BlockType.chrome.fields` / `collapsible`).
8
+
9
+ ## 0.5.0 - 2026-11-05
10
+
11
+ ### Changed
12
+
13
+ - **Breaking:** requires `@skaile/forge-compose` ^0.7.0. The `material` result's snippets and examples carry `max` (forge-compose `RankedEntry.max`, the highest attainable score for the target; `score / max` is the fit), and the `element` signal compares the target's element with each entry's own element, so an example learned for another diagnosis of the same case no longer counts as an element match. Hosts that build a `Material` by hand add `max` to each entry.
14
+
3
15
  ## 0.4.0 - 2026-11-05
4
16
 
5
17
  ### Changed
@@ -11,7 +11,17 @@ export interface ComposeBlockTypeOptions {
11
11
  }[];
12
12
  /** Display names of the four types; defaults are German. */
13
13
  labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>;
14
+ /**
15
+ * Look of the slot. `'plain'` (default): the plain Typed block of a case or instance document. `'template'`: for template
16
+ * documents, a gray card (`chrome.tone`) with a read-only field row under the title (identifier, `je`, `optional`, `selector`,
17
+ * first line of `anweisung`; a click opens the details) and a chevron that folds the body (`chrome.collapsible`).
18
+ */
19
+ slotChrome?: 'template' | 'plain';
20
+ /** Prefixes of the template field row (`slotChrome: 'template'`); defaults are German: `je`, `optional`, `Auswahl`, `KI`. */
21
+ slotFieldLabels?: Partial<Record<'je' | 'optional' | 'selector' | 'anweisung', string>>;
14
22
  }
23
+ /** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */
24
+ export declare const TEMPLATE_SLOT_TONE = "var(--sk-muted)";
15
25
  /** The four compose Block types. */
16
26
  export declare function composeBlockTypes(o?: ComposeBlockTypeOptions): BlockType[];
17
27
  /** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */
@@ -1,5 +1,41 @@
1
1
  import { KIND } from "@skaile/forge-compose/documents";
2
2
  //#region src/block-types.ts
3
+ /** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */
4
+ const TEMPLATE_SLOT_TONE = "var(--sk-muted)";
5
+ /** The chrome of a template slot (`slotChrome: 'template'`) with the field row prefixes `labels`. */
6
+ function templateSlotChrome(labels = {}) {
7
+ const label = {
8
+ je: "je",
9
+ optional: "optional",
10
+ selector: "Auswahl",
11
+ anweisung: "KI",
12
+ ...labels
13
+ };
14
+ return {
15
+ tone: TEMPLATE_SLOT_TONE,
16
+ collapsible: true,
17
+ fields: [
18
+ { key: "identifier" },
19
+ {
20
+ key: "je",
21
+ label: label.je
22
+ },
23
+ {
24
+ key: "optional",
25
+ label: label.optional,
26
+ flag: "ja"
27
+ },
28
+ {
29
+ key: "selector",
30
+ label: label.selector
31
+ },
32
+ {
33
+ key: "anweisung",
34
+ label: label.anweisung
35
+ }
36
+ ]
37
+ };
38
+ }
3
39
  /** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */
4
40
  const JA_NEIN = [{
5
41
  value: "ja",
@@ -101,7 +137,8 @@ function composeBlockTypes(o = {}) {
101
137
  id: "ai-brief",
102
138
  config: { ...SLOT_AI }
103
139
  }
104
- ]
140
+ ],
141
+ ...o.slotChrome === "template" && { chrome: templateSlotChrome(o.slotFieldLabels) }
105
142
  },
106
143
  {
107
144
  id: KIND.source,
@@ -231,6 +268,6 @@ function withProperties(blockType, definitions) {
231
268
  };
232
269
  }
233
270
  //#endregion
234
- export { composeBlockTypes, withAiBrief, withProperties };
271
+ export { TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
235
272
 
236
273
  //# sourceMappingURL=block-types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"block-types.js","names":[],"sources":["../src/block-types.ts"],"sourcesContent":["// Block types of the compose documents. Pure data (type-only imports besides `KIND`), so seeds, tests and server code use the\n// same ids and properties as the editor. `section` is forge-editor's built-in container type and is not defined here.\n//\n// slot one text block of a template (and of an instance made from it); `identifier` is what facts and examples target\n// source a text the facts and examples come from (a letter, a chapter, a case file)\n// fact one short statement of a source, assigned to a slot\n// example one reusable text for a slot, tagged by case (`alter/*`, `icd/*`, …); a proposal until a person accepts it\nimport type { BlockType } from '@skaile/forge-editor/composites'\nimport type { PropertyDefinition } from '@skaile/forge-editor/core'\nimport { KIND } from '@skaile/forge-compose/documents'\n\nexport type ComposeKind = typeof KIND.slot | typeof KIND.source | typeof KIND.fact | typeof KIND.example\n\nexport interface ComposeBlockTypeOptions {\n /** The slot identifiers a fact or example can target: `value` = identifier, `label` = what the person sees. Without options `slot` is a text field. */\n slotOptions?: { value: string; label: string }[]\n /** Display names of the four types; defaults are German. */\n labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>\n}\n\n/** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */\nconst JA_NEIN = [\n { value: 'ja', label: 'Ja' },\n { value: 'nein', label: 'Nein' },\n]\n\nconst BASKET = { id: 'basket' }\nconst TITLE_HIDDEN = { id: 'titleHidden' }\n\nconst DERIVATION: PropertyDefinition = { key: 'derivation', label: 'Herkunft', type: 'derivation' }\n\n/**\n * The slot type's AI brief. It has no `contentTemplate`: the editor leaves a `{key}` placeholder of the template literally in\n * the prompt when the property is empty, so a template of `{skelett}` would reach the model as the text „{skelett}“ for every\n * slot without a skeleton. The skeleton travels as an `aiBlockData` entry instead (`compose-material`), only when it is set.\n */\nconst SLOT_AI = {\n purpose: 'Ein Abschnitt eines Dokuments: sachlicher Fließtext, der nur aus den genannten Fakten entsteht; Beispiele sind Stilvorlage.',\n instructions: 'Schreibe sachlichen Fließtext nur aus den genannten Fakten. Beispiele sind Stilvorlage für Aufbau und Ton; erfinde nichts hinzu.',\n instructionsField: 'anweisung',\n}\n\n/** The four compose Block types. */\nexport function composeBlockTypes(o: ComposeBlockTypeOptions = {}): BlockType[] {\n const label = { slot: 'Abschnitt', source: 'Quelle', fact: 'Fakt', example: 'Beispiel', ...o.labels }\n const options = (o.slotOptions ?? []).filter(option => option.value !== '')\n\n // A select cannot hold an empty option (the Select component refuses an empty value): the placeholder shows while `slot` is unset.\n // A `slot` that is no longer in the template stays in the document but is not selectable.\n const slotTarget: PropertyDefinition = options.length > 0\n ? { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'select', options }\n : { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'text' }\n\n return [\n {\n id: KIND.slot,\n label: label.slot,\n icon: 'i-lucide-file-text',\n titlePlaceholder: label.slot,\n descriptionPlaceholder: 'Beschreibung: Zuordnung, Stichpunkte, Zweck der Prosa',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'identifier', label: 'Bezeichner (z. B. therapie/pflege)', type: 'text' },\n { key: 'optional', label: 'Optional (Slot darf in der Instanz entfallen)', type: 'select', options: JA_NEIN },\n { key: 'heading', label: 'Überschrift im Dokument', type: 'select', options: JA_NEIN },\n { key: 'anweisung', label: 'KI-Anweisung (ersetzt die Vorgabe)', type: 'textarea' },\n { key: 'skelett', label: 'Skelett (Zieltext als Markdown, {{…}} = freie Stellen)', type: 'textarea' },\n { key: 'selector', label: 'Auswahl (#muss ~bevorzugt -nicht)', type: 'text' },\n { key: 'je', label: 'Je Fall-Tag wiederholen (Facette, z. B. icd)', type: 'text' },\n ],\n },\n },\n { id: 'ai-brief', config: { ...SLOT_AI } },\n ],\n },\n {\n id: KIND.source,\n label: label.source,\n icon: 'i-lucide-file-search',\n titlePlaceholder: 'Titel der Quelle',\n descriptionPlaceholder: '',\n capabilities: [\n { id: 'container', config: {} },\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'sourceType', label: 'Quelltyp', type: 'text' },\n { key: 'date', label: 'Datum', type: 'text' },\n ],\n },\n },\n ],\n chrome: { tags: false },\n },\n {\n id: KIND.fact,\n label: label.fact,\n icon: 'i-lucide-lightbulb',\n titlePlaceholder: '',\n descriptionPlaceholder: '',\n capabilities: [BASKET, TITLE_HIDDEN, { id: 'properties', config: { definitions: [slotTarget, DERIVATION] } }],\n chrome: { tags: false },\n },\n {\n id: KIND.example,\n label: label.example,\n icon: 'i-lucide-quote',\n titlePlaceholder: label.example,\n descriptionPlaceholder: '',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n slotTarget,\n { key: 'status', label: 'Status', type: 'select', options: [\n { value: 'vorschlag', label: 'Vorschlag' },\n { value: 'geprüft', label: 'Geprüft' },\n ] },\n // Written by the split, shown as the proposal chip of the title row (`composeFeatures`) while `status` is `vorschlag`;\n // the properties form has no read-only field, so both are hidden from it and removed when the proposal is accepted.\n { key: 'confidence', label: 'Sicherheit', type: 'text', hidden: true },\n { key: 'origin', label: 'Herkunft der Zuordnung', type: 'text', hidden: true },\n DERIVATION,\n ],\n },\n },\n ],\n },\n ]\n}\n\n/** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */\nexport function withAiBrief(blockType: BlockType, brief: { purpose: string; instructions?: string }): BlockType {\n const config = (existing: unknown) => ({\n ...(existing && typeof existing === 'object' ? existing : {}),\n purpose: brief.purpose,\n ...(brief.instructions !== undefined && { instructions: brief.instructions }),\n })\n const listed = blockType.capabilities.some(capability => capability.id === 'ai-brief')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'ai-brief' ? { ...capability, config: config(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'ai-brief', config: config(undefined) }],\n }\n}\n\n/** `blockType` with `definitions` added to its `properties` capability; a definition whose key exists replaces it in place. Pure. */\nexport function withProperties(blockType: BlockType, definitions: readonly PropertyDefinition[]): BlockType {\n const merge = (existing: unknown): { definitions: PropertyDefinition[] } => {\n const current = (existing as { definitions?: readonly PropertyDefinition[] } | undefined)?.definitions ?? []\n const replaced = current.map(definition => definitions.find(added => added.key === definition.key) ?? definition)\n const added = definitions.filter(definition => !current.some(known => known.key === definition.key))\n return { definitions: [...replaced, ...added] }\n }\n const listed = blockType.capabilities.some(capability => capability.id === 'properties')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'properties' ? { ...capability, config: merge(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'properties', config: merge(undefined) }],\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"block-types.js","names":[],"sources":["../src/block-types.ts"],"sourcesContent":["// Block types of the compose documents. Pure data (type-only imports besides `KIND`), so seeds, tests and server code use the\n// same ids and properties as the editor. `section` is forge-editor's built-in container type and is not defined here.\n//\n// slot one text block of a template (and of an instance made from it); `identifier` is what facts and examples target\n// source a text the facts and examples come from (a letter, a chapter, a case file)\n// fact one short statement of a source, assigned to a slot\n// example one reusable text for a slot, tagged by case (`alter/*`, `icd/*`, …); a proposal until a person accepts it\nimport type { BlockType, BlockTypeChrome } from '@skaile/forge-editor/composites'\nimport type { PropertyDefinition } from '@skaile/forge-editor/core'\nimport { KIND } from '@skaile/forge-compose/documents'\n\nexport type ComposeKind = typeof KIND.slot | typeof KIND.source | typeof KIND.fact | typeof KIND.example\n\nexport interface ComposeBlockTypeOptions {\n /** The slot identifiers a fact or example can target: `value` = identifier, `label` = what the person sees. Without options `slot` is a text field. */\n slotOptions?: { value: string; label: string }[]\n /** Display names of the four types; defaults are German. */\n labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>\n /**\n * Look of the slot. `'plain'` (default): the plain Typed block of a case or instance document. `'template'`: for template\n * documents, a gray card (`chrome.tone`) with a read-only field row under the title (identifier, `je`, `optional`, `selector`,\n * first line of `anweisung`; a click opens the details) and a chevron that folds the body (`chrome.collapsible`).\n */\n slotChrome?: 'template' | 'plain'\n /** Prefixes of the template field row (`slotChrome: 'template'`); defaults are German: `je`, `optional`, `Auswahl`, `KI`. */\n slotFieldLabels?: Partial<Record<'je' | 'optional' | 'selector' | 'anweisung', string>>\n}\n\n/** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */\nexport const TEMPLATE_SLOT_TONE = 'var(--sk-muted)'\n\n/** The chrome of a template slot (`slotChrome: 'template'`) with the field row prefixes `labels`. */\nfunction templateSlotChrome(labels: ComposeBlockTypeOptions['slotFieldLabels'] = {}): BlockTypeChrome {\n const label = { je: 'je', optional: 'optional', selector: 'Auswahl', anweisung: 'KI', ...labels }\n return {\n tone: TEMPLATE_SLOT_TONE,\n collapsible: true,\n fields: [\n { key: 'identifier' },\n { key: 'je', label: label.je },\n { key: 'optional', label: label.optional, flag: 'ja' },\n { key: 'selector', label: label.selector },\n { key: 'anweisung', label: label.anweisung },\n ],\n }\n}\n\n/** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */\nconst JA_NEIN = [\n { value: 'ja', label: 'Ja' },\n { value: 'nein', label: 'Nein' },\n]\n\nconst BASKET = { id: 'basket' }\nconst TITLE_HIDDEN = { id: 'titleHidden' }\n\nconst DERIVATION: PropertyDefinition = { key: 'derivation', label: 'Herkunft', type: 'derivation' }\n\n/**\n * The slot type's AI brief. It has no `contentTemplate`: the editor leaves a `{key}` placeholder of the template literally in\n * the prompt when the property is empty, so a template of `{skelett}` would reach the model as the text „{skelett}“ for every\n * slot without a skeleton. The skeleton travels as an `aiBlockData` entry instead (`compose-material`), only when it is set.\n */\nconst SLOT_AI = {\n purpose: 'Ein Abschnitt eines Dokuments: sachlicher Fließtext, der nur aus den genannten Fakten entsteht; Beispiele sind Stilvorlage.',\n instructions: 'Schreibe sachlichen Fließtext nur aus den genannten Fakten. Beispiele sind Stilvorlage für Aufbau und Ton; erfinde nichts hinzu.',\n instructionsField: 'anweisung',\n}\n\n/** The four compose Block types. */\nexport function composeBlockTypes(o: ComposeBlockTypeOptions = {}): BlockType[] {\n const label = { slot: 'Abschnitt', source: 'Quelle', fact: 'Fakt', example: 'Beispiel', ...o.labels }\n const options = (o.slotOptions ?? []).filter(option => option.value !== '')\n\n // A select cannot hold an empty option (the Select component refuses an empty value): the placeholder shows while `slot` is unset.\n // A `slot` that is no longer in the template stays in the document but is not selectable.\n const slotTarget: PropertyDefinition = options.length > 0\n ? { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'select', options }\n : { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'text' }\n\n return [\n {\n id: KIND.slot,\n label: label.slot,\n icon: 'i-lucide-file-text',\n titlePlaceholder: label.slot,\n descriptionPlaceholder: 'Beschreibung: Zuordnung, Stichpunkte, Zweck der Prosa',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'identifier', label: 'Bezeichner (z. B. therapie/pflege)', type: 'text' },\n { key: 'optional', label: 'Optional (Slot darf in der Instanz entfallen)', type: 'select', options: JA_NEIN },\n { key: 'heading', label: 'Überschrift im Dokument', type: 'select', options: JA_NEIN },\n { key: 'anweisung', label: 'KI-Anweisung (ersetzt die Vorgabe)', type: 'textarea' },\n { key: 'skelett', label: 'Skelett (Zieltext als Markdown, {{…}} = freie Stellen)', type: 'textarea' },\n { key: 'selector', label: 'Auswahl (#muss ~bevorzugt -nicht)', type: 'text' },\n { key: 'je', label: 'Je Fall-Tag wiederholen (Facette, z. B. icd)', type: 'text' },\n ],\n },\n },\n { id: 'ai-brief', config: { ...SLOT_AI } },\n ],\n ...(o.slotChrome === 'template' && { chrome: templateSlotChrome(o.slotFieldLabels) }),\n },\n {\n id: KIND.source,\n label: label.source,\n icon: 'i-lucide-file-search',\n titlePlaceholder: 'Titel der Quelle',\n descriptionPlaceholder: '',\n capabilities: [\n { id: 'container', config: {} },\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'sourceType', label: 'Quelltyp', type: 'text' },\n { key: 'date', label: 'Datum', type: 'text' },\n ],\n },\n },\n ],\n chrome: { tags: false },\n },\n {\n id: KIND.fact,\n label: label.fact,\n icon: 'i-lucide-lightbulb',\n titlePlaceholder: '',\n descriptionPlaceholder: '',\n capabilities: [BASKET, TITLE_HIDDEN, { id: 'properties', config: { definitions: [slotTarget, DERIVATION] } }],\n chrome: { tags: false },\n },\n {\n id: KIND.example,\n label: label.example,\n icon: 'i-lucide-quote',\n titlePlaceholder: label.example,\n descriptionPlaceholder: '',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n slotTarget,\n { key: 'status', label: 'Status', type: 'select', options: [\n { value: 'vorschlag', label: 'Vorschlag' },\n { value: 'geprüft', label: 'Geprüft' },\n ] },\n // Written by the split, shown as the proposal chip of the title row (`composeFeatures`) while `status` is `vorschlag`;\n // the properties form has no read-only field, so both are hidden from it and removed when the proposal is accepted.\n { key: 'confidence', label: 'Sicherheit', type: 'text', hidden: true },\n { key: 'origin', label: 'Herkunft der Zuordnung', type: 'text', hidden: true },\n DERIVATION,\n ],\n },\n },\n ],\n },\n ]\n}\n\n/** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */\nexport function withAiBrief(blockType: BlockType, brief: { purpose: string; instructions?: string }): BlockType {\n const config = (existing: unknown) => ({\n ...(existing && typeof existing === 'object' ? existing : {}),\n purpose: brief.purpose,\n ...(brief.instructions !== undefined && { instructions: brief.instructions }),\n })\n const listed = blockType.capabilities.some(capability => capability.id === 'ai-brief')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'ai-brief' ? { ...capability, config: config(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'ai-brief', config: config(undefined) }],\n }\n}\n\n/** `blockType` with `definitions` added to its `properties` capability; a definition whose key exists replaces it in place. Pure. */\nexport function withProperties(blockType: BlockType, definitions: readonly PropertyDefinition[]): BlockType {\n const merge = (existing: unknown): { definitions: PropertyDefinition[] } => {\n const current = (existing as { definitions?: readonly PropertyDefinition[] } | undefined)?.definitions ?? []\n const replaced = current.map(definition => definitions.find(added => added.key === definition.key) ?? definition)\n const added = definitions.filter(definition => !current.some(known => known.key === definition.key))\n return { definitions: [...replaced, ...added] }\n }\n const listed = blockType.capabilities.some(capability => capability.id === 'properties')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'properties' ? { ...capability, config: merge(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'properties', config: merge(undefined) }],\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { ComposeBlockTypeOptions, ComposeKind, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
1
+ import { ComposeBlockTypeOptions, ComposeKind, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
2
2
  import { KIND } from "@skaile/forge-compose/documents";
3
- export { type ComposeBlockTypeOptions, type ComposeKind, KIND, composeBlockTypes, withAiBrief, withProperties };
3
+ export { type ComposeBlockTypeOptions, type ComposeKind, KIND, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
1
+ import { TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
2
2
  import { KIND } from "@skaile/forge-compose/documents";
3
- export { KIND, composeBlockTypes, withAiBrief, withProperties };
3
+ export { KIND, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skaile/forge-editor-compose",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Corpus to composed text in @skaile/forge-editor: the Block types slot, source, fact and example, the editor features that split sources into examples, extract facts, fill slots with ranked material and adopt examples, and the operation input contract shared with the host's routes.",
6
6
  "license": "MIT",
@@ -50,11 +50,11 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@skaile/forge-compose": "^0.4.0",
53
+ "@skaile/forge-compose": "^0.7.0",
54
54
  "zod": "^4.5.4"
55
55
  },
56
56
  "peerDependencies": {
57
- "@skaile/forge-editor": "^6.2.0",
57
+ "@skaile/forge-editor": "^6.7.0",
58
58
  "@skaile/forge-app-actions": "^0.4.1",
59
59
  "@tiptap/core": "^3.31.3",
60
60
  "@tiptap/pm": "^3.31.3",