sysml-diagram 0.14.5 → 0.15.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/out/main.js CHANGED
@@ -161748,6 +161748,12 @@ function keywordFor(node) {
161748
161748
  const parallel = isParallelState(node) ? "parallel " : "";
161749
161749
  const leadMods = [variability, temporal].filter(Boolean);
161750
161750
  const prefix = leadMods.length ? leadMods.map((m) => `${m} `).join("") : parallel;
161751
+ if (t === "BareDefDecl")
161752
+ return `${prefix}def`.trim();
161753
+ if (t === "FeatureShorthand") {
161754
+ const kinds = (Array.isArray(n2.usageKinds) ? n2.usageKinds : []).join(" ");
161755
+ return `${prefix}${kinds}`.trim() || "feature";
161756
+ }
161751
161757
  if (t === "ActorDecl" || t === "SubjectDecl" || t === "StakeholderDecl")
161752
161758
  return `${prefix}${word}`;
161753
161759
  return def ? `${prefix}${word} def` : `${prefix}${word}`;
@@ -161756,7 +161762,39 @@ var isDefinitionKind = (node) => isPartDecl(node) || isPortDecl(node) || isItemD
161756
161762
  // genuine SysML definitions (FlowConnectionDefinition / AssociationStructure /
161757
161763
  // AllocationDefinition) and belong in the General View like any other def.
161758
161764
  isFlowStmt(node) || isConnectorDecl(node) || isAllocationDecl(node) || isConstraintDecl(node) || isEnumDecl(node) || isStateDecl(node) || isActionDecl(node) || isCalcDecl(node) || isConcernDecl(node) || isViewDecl(node) || isViewpointDecl(node) || isRenderingDecl(node) || isMetadataDecl(node);
161759
- var isFeatureUsageMember = (m, owner) => isPartDecl(m) || isPortDecl(m) || isItemDecl(m) || isAttributeDecl(m) || isOccurrenceDecl(m) || isStateDecl(m) && !isExhibitStateDecl(m) || isActionDecl(m) || isCalcDecl(m) || isConstraintDecl(m) || isRequirementDecl(m) || isConcernDecl(m) || isViewDecl(m) || isViewpointDecl(m) || isRenderingDecl(m) || isUseCaseDecl(m) || isAnalysisCaseDecl(m) || isVerificationCaseDecl(m) || isCaseDecl(m) || isConnectionDecl(m) || isInterfaceDecl(m) || isAllocationDecl(m) || isEnumDecl(m) && !isEnumDecl(owner);
161765
+ var isBareDefDecl = (node) => node.$type === "BareDefDecl";
161766
+ var isDefinitionNode = (node) => isDefinitionKind(node) && node.isDef === true || isBareDefDecl(node);
161767
+ var modifiersOf = (node) => {
161768
+ const mods = node.modifiers;
161769
+ return Array.isArray(mods) ? mods : [];
161770
+ };
161771
+ var portionKindOf = (node) => modifiersOf(node).find((m) => m === "timeslice" || m === "snapshot");
161772
+ var isIndividualOccurrence = (node) => modifiersOf(node).includes("individual");
161773
+ var isOccurrenceModified = (node) => portionKindOf(node) !== void 0 || isIndividualOccurrence(node);
161774
+ var redefinedNameOf = (node) => {
161775
+ const n2 = node;
161776
+ for (const rel2 of [...n2.preRelationships ?? [], ...n2.relationships ?? []]) {
161777
+ if (rel2.kind === ":>>" || rel2.kind === "redefines") {
161778
+ const target = (rel2.targets ?? [])[0];
161779
+ if (target)
161780
+ return lastSeg(target);
161781
+ }
161782
+ }
161783
+ return void 0;
161784
+ };
161785
+ var portionLabelOf = (node) => nameOf2(node) ?? (isOccurrenceModified(node) ? redefinedNameOf(node) : void 0);
161786
+ function diagramNodeId(node) {
161787
+ const name = nameOf2(node);
161788
+ if (name)
161789
+ return qnameOf(node) || name;
161790
+ const label = portionLabelOf(node);
161791
+ if (!label)
161792
+ return void 0;
161793
+ const ownerQname = qnameOf(node);
161794
+ return ownerQname ? `${ownerQname}::${label}` : label;
161795
+ }
161796
+ var isDeclaredEventOccurrence = (m) => isEventDecl(m) && m.isOccurrence === true;
161797
+ var isFeatureUsageMember = (m, owner) => isPartDecl(m) || isPortDecl(m) || isItemDecl(m) || isAttributeDecl(m) || isOccurrenceDecl(m) || isDeclaredEventOccurrence(m) || isStateDecl(m) && !isExhibitStateDecl(m) || isActionDecl(m) || isCalcDecl(m) || isConstraintDecl(m) || isRequirementDecl(m) || isConcernDecl(m) || isViewDecl(m) || isViewpointDecl(m) || isRenderingDecl(m) || isUseCaseDecl(m) || isAnalysisCaseDecl(m) || isVerificationCaseDecl(m) || isCaseDecl(m) || isConnectionDecl(m) || isInterfaceDecl(m) || isAllocationDecl(m) || isEnumDecl(m) && !isEnumDecl(owner);
161760
161798
  function specializationTargets(node) {
161761
161799
  const n2 = node;
161762
161800
  const out = [];
@@ -162316,7 +162354,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162316
162354
  return false;
162317
162355
  }
162318
162356
  const scope = this.packageScopeOf(node);
162319
- return [...ast_utils_exports.streamAllContents(scope)].some((n2) => isDefinitionKind(n2) && n2.isDef === true && nameOf2(n2) || this.isViewlessUsage(n2) && this.isDirectPackageMember(n2) || isPartDecl(n2) && n2.isDef !== true && !!nameOf2(n2) && this.isDirectPackageMember(n2));
162357
+ return [...ast_utils_exports.streamAllContents(scope)].some((n2) => isDefinitionNode(n2) && nameOf2(n2) || this.isViewlessUsage(n2) && this.isDirectPackageMember(n2) || isPartDecl(n2) && n2.isDef !== true && !!nameOf2(n2) && this.isDirectPackageMember(n2));
162320
162358
  }
162321
162359
  case "iv":
162322
162360
  if (isPartDecl(node)) {
@@ -162387,6 +162425,8 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162387
162425
  // they get their graphical home as General View usage nodes — previously a
162388
162426
  // package-level `constraint massLimitation { … }` was invisible everywhere.
162389
162427
  isViewlessUsage(n2) {
162428
+ if (n2.$type === "FeatureShorthand" && isOccurrenceModified(n2) && nameOf2(n2) !== void 0)
162429
+ return true;
162390
162430
  return (isConstraintDecl(n2) || isCalcDecl(n2) || isOccurrenceDecl(n2) || isItemDecl(n2) || isAttributeDecl(n2)) && n2.isDef !== true && nameOf2(n2) !== void 0;
162391
162431
  }
162392
162432
  isDirectPackageMember(n2) {
@@ -162411,9 +162451,9 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162411
162451
  const { uri, index: index2 } = ctx;
162412
162452
  const scope = this.packageScopeOf(ctx.anchor);
162413
162453
  const all = [...ast_utils_exports.streamAllContents(scope)];
162414
- const defNodes = all.filter((n2) => isDefinitionKind(n2) && n2.isDef === true && nameOf2(n2));
162454
+ const defNodes = all.filter((n2) => isDefinitionNode(n2) && nameOf2(n2));
162415
162455
  const packageOverview = isPackage(ctx.anchor) || isDocument(ctx.anchor);
162416
- const hasOwnedMemberNode = all.some((n2) => isPackage(n2) && !!nameOf2(n2) || isDefinitionKind(n2) && n2.isDef === true && !!nameOf2(n2) || this.isViewlessUsage(n2) && this.isDirectPackageMember(n2) || isPartDecl(n2) && n2.isDef !== true && !!nameOf2(n2) && this.isDirectPackageMember(n2));
162456
+ const hasOwnedMemberNode = all.some((n2) => isPackage(n2) && !!nameOf2(n2) || isDefinitionNode(n2) && !!nameOf2(n2) || this.isViewlessUsage(n2) && this.isDirectPackageMember(n2) || isPartDecl(n2) && n2.isDef !== true && !!nameOf2(n2) && this.isDirectPackageMember(n2));
162417
162457
  const packageNodes = [
162418
162458
  ...packageOverview && hasOwnedMemberNode && isPackage(scope) && nameOf2(scope) ? [scope] : [],
162419
162459
  ...all.filter((p) => isPackage(p) && nameOf2(p))
@@ -162603,16 +162643,18 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162603
162643
  const boundReferences = [];
162604
162644
  for (let w = 0; w < featureWork.length; w += 1) {
162605
162645
  const d = featureWork[w];
162606
- for (const m of membersOf(d)) {
162646
+ for (const raw of membersOf(d)) {
162647
+ const m = this.thenActionMemberNode(raw) ?? raw;
162607
162648
  const mm = m;
162608
- if (mm.isDef === true || !nameOf2(m))
162649
+ const label = portionLabelOf(m);
162650
+ if (mm.isDef === true || !label)
162609
162651
  continue;
162610
162652
  if (directionOf(m) !== void 0)
162611
162653
  continue;
162612
- if (!isFeatureUsageMember(m, d))
162654
+ if (!isFeatureUsageMember(m, d) && !isOccurrenceModified(m))
162613
162655
  continue;
162614
162656
  const isRefMembership = (mm.modifiers ?? []).includes("ref");
162615
- if (!isPartDecl(m) && !isOccurrenceDecl(m))
162657
+ if (!isPartDecl(m) && !isOccurrenceDecl(m) && !isOccurrenceModified(m) && !isDeclaredEventOccurrence(m))
162616
162658
  continue;
162617
162659
  const boundTarget = isRefMembership ? boundPartTarget(m) : void 0;
162618
162660
  const hasBoundPath = isRefMembership && isPathExpr(mm.value);
@@ -162625,14 +162667,16 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162625
162667
  }
162626
162668
  const def = this.resolveType(m, index2);
162627
162669
  const defOnCanvas = !!(def && ids.has(def));
162628
- const uid = qnameOf(m) || nameOf2(m);
162670
+ const uid = diagramNodeId(m) ?? label;
162629
162671
  if (!ids.has(m)) {
162630
162672
  ids.set(m, uid);
162631
- if (!byName.has(nameOf2(m)))
162632
- byName.set(nameOf2(m), m);
162673
+ if (!byName.has(label))
162674
+ byName.set(label, m);
162675
+ if (!byQName.has(uid))
162676
+ byQName.set(uid, m);
162633
162677
  nodes.push({
162634
162678
  id: uid,
162635
- name: nameOf2(m),
162679
+ name: label,
162636
162680
  keyword: keywordFor(m),
162637
162681
  isDef: false,
162638
162682
  shape: "box",
@@ -162657,10 +162701,16 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162657
162701
  id: `e${e++}`,
162658
162702
  from: ids.get(d),
162659
162703
  to: uid,
162660
- kind: isRefMembership ? "reference" : "composition",
162704
+ // REQ-160 a composed `timeslice`/`snapshot` is not ordinary
162705
+ // feature membership: it is the temporal decomposition of the
162706
+ // owner's life (OMG 8.2.3.9), drawn with the hollow triangle at
162707
+ // the whole end. A `ref` portion still reads as a reference — it
162708
+ // points at a portion owned elsewhere rather than carving up this
162709
+ // occurrence's own lifetime.
162710
+ kind: isRefMembership ? "reference" : portionKindOf(m) ? "portion" : "composition",
162661
162711
  // No label — the usage name/type read inside the box; keep
162662
162712
  // `name` for the stable route key and Properties.
162663
- name: nameOf2(m),
162713
+ name: label,
162664
162714
  type: typeText(m),
162665
162715
  source: sourceOf2(m, uri)
162666
162716
  });
@@ -162669,6 +162719,31 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162669
162719
  }
162670
162720
  }
162671
162721
  }
162722
+ for (const owner of [...ids.keys()]) {
162723
+ const from = ids.get(owner);
162724
+ for (const raw of membersOf(owner)) {
162725
+ const m = this.thenActionMemberNode(raw) ?? raw;
162726
+ if (!isEventDecl(m) || m.isOccurrence === true)
162727
+ continue;
162728
+ const ev = m;
162729
+ const path10 = [nameOf2(m), ...ev.chainSegs].filter(Boolean).join("::");
162730
+ if (!path10)
162731
+ continue;
162732
+ const target = resolveDrawnByText(path10);
162733
+ const to = target && ids.get(target);
162734
+ if (!to || to === from)
162735
+ continue;
162736
+ edges.push({
162737
+ id: `e${e++}`,
162738
+ from,
162739
+ to,
162740
+ kind: "referenceSubsetting",
162741
+ label: "event",
162742
+ name: lastSeg(path10),
162743
+ source: sourceOf2(m, uri)
162744
+ });
162745
+ }
162746
+ }
162672
162747
  for (const binding of boundReferences) {
162673
162748
  const from = ids.get(binding.owner);
162674
162749
  const to = ids.get(binding.target);
@@ -165863,7 +165938,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165863
165938
  const related = this.relatedNodes(anchor, index2);
165864
165939
  const keepIds = /* @__PURE__ */ new Set();
165865
165940
  for (const n2 of related) {
165866
- const id2 = qnameOf(n2) || nameOf2(n2);
165941
+ const id2 = diagramNodeId(n2);
165867
165942
  if (id2)
165868
165943
  keepIds.add(id2);
165869
165944
  }
@@ -166073,7 +166148,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166073
166148
  // One registry: each compartment is defined once with the member predicate
166074
166149
  // that fills it, emitted in a single canonical order that satisfies the
166075
166150
  // requirement order (doc → subject → attributes → assume → require →
166076
- // actors → stakeholders → frame concerns, REQ-183) and the use-case order
166151
+ // actors → stakeholders → frames, REQ-183) and the use-case order
166077
166152
  // (subject → actors → include use cases, REQ-184). Empty compartments are
166078
166153
  // omitted (REQ-181) — except `subject`, which is mandatory on requirement /
166079
166154
  // use-case / verification-case definitions (REQ-183/184) and shows a
@@ -166087,7 +166162,10 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166087
166162
  const t = typeText(m);
166088
166163
  return `${dir ? `${dir} ` : ""}${derived}${nameOf2(m) ?? ""}${t ? ` : ${conj}${t}` : ""}`.trim();
166089
166164
  }
166090
- /** REQ-177/REQ-187 — inherited features (`^name`), dropped when redefined locally. */
166165
+ /** REQ-177/REQ-187 — inherited attributes, dropped when redefined locally.
166166
+ * Their textual home is selected later by the same classifier as local
166167
+ * members; retaining the AST member here prevents direction/variability
166168
+ * from being flattened into `attributes` before classification. */
166091
166169
  inheritedItems(node, index2, uri) {
166092
166170
  if (node.isDef === true)
166093
166171
  return [];
@@ -166104,22 +166182,45 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166104
166182
  }
166105
166183
  }
166106
166184
  const localNames = new Set(membersOf(node).map((m) => nameOf2(m)).filter(Boolean));
166107
- return membersOf(def).filter((m) => isAttributeDecl(m) && m.isDef !== true && nameOf2(m)).filter((m) => !localRedefined.has(nameOf2(m)) && !localNames.has(nameOf2(m))).map((m) => ({ text: `^${this.featureText(m)}`, source: sourceOf2(m, uri) }));
166185
+ return membersOf(def).filter((m) => isAttributeDecl(m) && m.isDef !== true && nameOf2(m)).filter((m) => !localRedefined.has(nameOf2(m)) && !localNames.has(nameOf2(m))).map((member) => ({ member, source: sourceOf2(member, uri) }));
166108
166186
  }
166109
166187
  compartmentsFor(node, uri, index2, featureRows = false, includeInherited = true) {
166110
166188
  const members = membersOf(node);
166111
166189
  const n2 = node;
166112
166190
  const item = (m, text) => ({ text: text ?? this.featureText(m), source: sourceOf2(m, uri) });
166113
- const usages = (guard) => members.filter((m) => guard(m) && m.isDef !== true && nameOf2(m));
166191
+ const isVariantConfigMember = (m) => m.allVariants === true;
166192
+ const isVariantMember = (m) => modifiersOf(m).includes("variant") && !isVariantConfigMember(m);
166193
+ const isNonVariantMember = (m) => !isVariantMember(m) && !isVariantConfigMember(m);
166194
+ const isOrdinaryMember = (m) => directionOf(m) === void 0 && isNonVariantMember(m);
166195
+ const inherited = includeInherited ? this.inheritedItems(node, index2, uri) : [];
166196
+ const inheritedRows = (predicate, text = (member) => this.featureText(member)) => inherited.filter(({ member }) => predicate(member)).map(({ member, source }) => ({ text: `^${text(member)}`, source }));
166197
+ const usages = (guard) => members.filter((m) => guard(m) && m.isDef !== true && nameOf2(m) && isOrdinaryMember(m));
166114
166198
  const exprText3 = (m) => (m.body?.$cstNode?.text ?? "").replace(/\s+/g, " ").trim();
166115
166199
  const statementText = (m) => (m.$cstNode?.text ?? this.featureText(m)).replace(/\s+/g, " ").replace(/;$/, "").trim();
166200
+ const canonicalFeatureText = (m) => m.$type === "FeatureShorthand" || m.$type === "FeatureRedefinitionShorthand" ? statementText(m) : this.featureText(m);
166116
166201
  const out = [];
166202
+ const claimedSources = /* @__PURE__ */ new Set();
166203
+ const sourceKey = (source) => `${source.uri}:${source.range.start.line}:${source.range.start.character}:${source.range.end.line}:${source.range.end.character}`;
166117
166204
  const add = (title, items) => {
166118
- if (items.length)
166119
- out.push({ title, items });
166205
+ const accepted = items.filter((candidate) => {
166206
+ if (!candidate.source)
166207
+ return true;
166208
+ const key = sourceKey(candidate.source);
166209
+ if (claimedSources.has(key))
166210
+ return false;
166211
+ claimedSources.add(key);
166212
+ return true;
166213
+ });
166214
+ if (!accepted.length)
166215
+ return;
166216
+ const existing = out.find((compartment) => compartment.title === title);
166217
+ if (existing)
166218
+ existing.items.push(...accepted);
166219
+ else
166220
+ out.push({ title, items: accepted });
166120
166221
  };
166121
166222
  add("doc", members.filter((m) => m.$type === "DocCommentMember").map((m) => item(m, notePreview(docCommentBody(m)) ?? "doc")));
166122
- const subjects = members.filter(isSubjectDecl);
166223
+ const subjects = members.filter(isSubjectDecl).filter(isNonVariantMember);
166123
166224
  const subjectMandatory = n2.isDef === true && (isRequirementDecl(node) || isUseCaseDecl(node) || isVerificationCaseDecl(node));
166124
166225
  if (subjects.length) {
166125
166226
  add("subject", subjects.map((s) => item(s)));
@@ -166128,34 +166229,53 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166128
166229
  }
166129
166230
  add("attributes", [
166130
166231
  ...usages(isAttributeDecl).map((a2) => item(a2)),
166131
- ...includeInherited ? this.inheritedItems(node, index2, uri) : []
166232
+ ...inheritedRows((member) => isAttributeDecl(member) && isOrdinaryMember(member))
166132
166233
  ]);
166234
+ const notPortion = (m) => portionKindOf(m) === void 0;
166133
166235
  if (featureRows) {
166134
166236
  add("ports", usages(isPortDecl).map((m) => item(m)));
166135
- add("parts", usages(isPartDecl).map((m) => item(m)));
166237
+ add("parts", usages(isPartDecl).filter(notPortion).map((m) => item(m)));
166136
166238
  }
166137
- add("features", members.filter((m) => m.$type === "FeatureShorthand" || m.$type === "FeatureRedefinitionShorthand").map((m) => item(m, statementText(m))));
166138
- const undirected = (guard) => usages(guard).filter((m) => directionOf(m) === void 0);
166139
- add("items", undirected(isItemDecl).map((m) => item(m)));
166140
- const occMods = (m) => m.modifiers ?? [];
166141
- add("occurrences", undirected(isOccurrenceDecl).filter((m) => !occMods(m).includes("timeslice") && !occMods(m).includes("snapshot")).map((m) => item(m)));
166239
+ add("features", members.filter((m) => m.$type === "FeatureShorthand" || m.$type === "FeatureRedefinitionShorthand").filter(isOrdinaryMember).filter((m) => !isOccurrenceModified(m)).map((m) => item(m, canonicalFeatureText(m))));
166240
+ const undirected = (guard) => usages(guard);
166241
+ add("items", undirected(isItemDecl).filter(notPortion).map((m) => item(m)));
166242
+ add("occurrences", undirected(isOccurrenceDecl).filter(notPortion).map((m) => item(m)));
166243
+ add("occurrences", members.map((m) => this.thenActionMemberNode(m) ?? m).filter(isNonVariantMember).filter((m) => isEventDecl(m) && m.isOccurrence === true && nameOf2(m)).map((m) => item(m)));
166142
166244
  add("calcs", undirected(isCalcDecl).map((m) => item(m)));
166143
166245
  add("constraints", undirected(isConstraintDecl).map((m) => item(m)));
166144
166246
  add("assume constraints", members.filter((m) => m.$type === "AssumeConstraintStmt").map((m) => item(m, exprText3(m) || "assume constraint")));
166145
166247
  add("require constraints", members.filter((m) => m.$type === "RequireConstraintStmt").map((m) => item(m, exprText3(m) || `require ${this.featurePath(m.target) ?? ""}`.trim())));
166146
- add("actors", members.filter(isActorDecl).map((m) => item(m)));
166147
- add("stakeholders", members.filter(isStakeholderDecl).map((m) => item(m)));
166148
- add("frame concerns", members.filter((m) => m.$type === "FrameMember").map((m) => item(m, this.refText(m.target) ?? "concern")));
166248
+ add("actors", members.filter(isActorDecl).filter(isNonVariantMember).map((m) => item(m)));
166249
+ add("stakeholders", members.filter(isStakeholderDecl).filter(isNonVariantMember).map((m) => item(m)));
166250
+ add("frames", members.filter((m) => m.$type === "FrameMember").map((m) => item(m, this.refText(m.target) ?? "concern")));
166149
166251
  add("include use cases", members.filter((m) => m.$type === "IncludeStmt").map((m) => item(m, this.refText(m.target) ?? "")));
166150
- add("objective", members.filter(isObjectiveDecl).map((m) => item(m, nameOf2(m) ?? "objective")));
166252
+ add("objective", members.filter(isObjectiveDecl).filter(isNonVariantMember).map((m) => item(m, nameOf2(m) ?? "objective")));
166151
166253
  add("verifies", members.filter((m) => m.$type === "VerifyStmt").map((m) => item(m, String(m.target ?? ""))));
166152
166254
  add("verification methods", members.filter((m) => isMetadataDecl(m) && (typeText(m) ?? "").includes("VerificationMethod")).map((m) => item(m, typeText(m) ?? "method")));
166153
- add("parameters", members.filter((m) => directionOf(m) !== void 0 && nameOf2(m)).map((m) => item(m)));
166154
- add("return", members.filter((m) => m.$type === "ReturnDecl").map((m) => item(m, `return${nameOf2(m) ? ` ${nameOf2(m)}` : ""}${typeText(m) ? ` : ${typeText(m)}` : ""}`)));
166255
+ const behavioralParameterOwner = isActionDecl(node) || isCalcDecl(node) || isConstraintDecl(node) || isCaseDecl(node) || isAnalysisCaseDecl(node) || isVerificationCaseDecl(node) || isUseCaseDecl(node);
166256
+ const enclosingBehavioralParameterOwner = (() => {
166257
+ if (isPartDecl(node) || isPortDecl(node) || directionOf(node) === void 0)
166258
+ return false;
166259
+ let owner = node.$container;
166260
+ while (owner) {
166261
+ if (isActionDecl(owner) || isCalcDecl(owner) || isConstraintDecl(owner) || isCaseDecl(owner) || isAnalysisCaseDecl(owner) || isVerificationCaseDecl(owner) || isUseCaseDecl(owner))
166262
+ return true;
166263
+ if (isPartDecl(owner) || isPortDecl(owner))
166264
+ return false;
166265
+ owner = owner.$container;
166266
+ }
166267
+ return false;
166268
+ })();
166269
+ const directedTitle = behavioralParameterOwner || enclosingBehavioralParameterOwner ? "parameters" : "directed features";
166270
+ add(directedTitle, [
166271
+ ...members.filter((m) => directionOf(m) !== void 0 && nameOf2(m)).filter((m) => !isVariantMember(m) && !isVariantConfigMember(m)).map((m) => item(m, canonicalFeatureText(m))),
166272
+ ...inheritedRows((member) => directionOf(member) !== void 0 && isNonVariantMember(member))
166273
+ ]);
166274
+ add("result", members.filter((m) => m.$type === "ReturnDecl").map((m) => item(m, `return${nameOf2(m) ? ` ${nameOf2(m)}` : ""}${typeText(m) ? ` : ${typeText(m)}` : ""}`)));
166155
166275
  if (isConstraintDecl(node))
166156
166276
  add("expression", exprText3(node) ? [{ text: exprText3(node), source: sourceOf2(node, uri) }] : []);
166157
- const enumValueMembers = members.filter((m) => m.$type === "EnumValueDecl" && (isEnumDecl(node) || !m.typing) || isEnumDecl(node) && isEnumDecl(m) && m.isDef !== true);
166158
- add("enumerations", enumValueMembers.map((m) => {
166277
+ const enumValueMembers = members.filter(isNonVariantMember).filter((m) => m.$type === "EnumValueDecl" && (isEnumDecl(node) || !m.typing) || isEnumDecl(node) && isEnumDecl(m) && m.isDef !== true);
166278
+ add("enums", enumValueMembers.map((m) => {
166159
166279
  const nm = nameOf2(m);
166160
166280
  if (nm)
166161
166281
  return item(m, nm);
@@ -166165,7 +166285,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166165
166285
  add("states", usages(isStateDecl).map((m) => item(m)));
166166
166286
  const stateActions = members.map((m) => ({ m, kind: { EntryAction: "entry", DoAction: "do", ExitAction: "exit" }[m.$type] })).filter((x) => !!x.kind);
166167
166287
  add("state actions", stateActions.map(({ m, kind }) => item(m, `${kind} ${nameOf2(m) ?? ""}`.trim())));
166168
- add("transitions", members.filter((m) => m.$type === "TransitionDecl").map((m) => item(m, `${m.from ?? ""} \u2192 ${m.to ?? ""}`)));
166288
+ add("state transition", members.filter((m) => m.$type === "TransitionDecl").map((m) => item(m, `${m.from ?? ""} \u2192 ${m.to ?? ""}`)));
166169
166289
  add("assert constraints", members.filter((m) => m.$type === "AssertConstraintStmt").map((m) => item(m, exprText3(m) || nameOf2(m) || "assert constraint")));
166170
166290
  add("invariants", members.filter((m) => m.$type === "InvShorthand").map((m) => {
166171
166291
  const inv = m;
@@ -166173,15 +166293,22 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166173
166293
  const expr = exprText3(m);
166174
166294
  return item(m, `${head2} { ${expr} }`.replace(/\s+/g, " ").trim());
166175
166295
  }));
166176
- add("action flow", usages(isActionDecl).map((m) => item(m)));
166296
+ add("actions", usages(isActionDecl).map((m) => item(m)));
166177
166297
  add("concerns", usages(isConcernDecl).map((m) => item(m)));
166178
166298
  add("viewpoints", usages(isViewpointDecl).map((m) => item(m)));
166179
- add("exposes", members.filter((m) => m.$type === "ExposeMember").flatMap((m) => (m.paths ?? []).map((p) => item(m, this.featurePath(p) ?? ""))));
166299
+ add("exposes", members.filter((m) => m.$type === "ExposeMember").flatMap((m) => (m.paths ?? []).map((path10) => item(path10, this.featurePath(path10) ?? (path10.$cstNode?.text ?? "").replace(/\s+/g, " ").trim()))));
166180
166300
  add("filters", members.filter((m) => m.$type === "FilterMember").map((m) => item(m, (m.$cstNode?.text ?? "filter").replace(/\s+/g, " ").replace(/^filter\s*/, "").replace(/;$/, "").trim())));
166181
166301
  add("rendering", members.filter((m) => m.$type === "RenderMember").map((m) => item(m, this.refText(m.target) ?? "")));
166182
- const withMod = (mod) => members.filter((m) => (m.modifiers ?? []).includes(mod) && nameOf2(m));
166183
- add("timeslices", withMod("timeslice").map((m) => item(m)));
166184
- add("snapshots", withMod("snapshot").map((m) => item(m)));
166302
+ const portionMembers = members.map((m) => this.thenActionMemberNode(m) ?? m).filter(isNonVariantMember);
166303
+ const withPortionKind = (kind) => portionMembers.filter((m) => portionKindOf(m) === kind && portionLabelOf(m));
166304
+ const portionText = (m) => {
166305
+ const t = typeText(m);
166306
+ const mult = multText(m);
166307
+ return `${portionLabelOf(m)}${t ? ` : ${t}` : ""}${mult ? ` ${mult}` : ""}`;
166308
+ };
166309
+ add("timeslices", withPortionKind("timeslice").map((m) => item(m, portionText(m))));
166310
+ add("snapshots", withPortionKind("snapshot").map((m) => item(m, portionText(m))));
166311
+ add("individuals", portionMembers.filter((m) => m.$type === "FeatureShorthand" && isIndividualOccurrence(m) && portionKindOf(m) === void 0 && portionLabelOf(m)).map((m) => item(m, portionText(m))));
166185
166312
  const metaBadges = [
166186
166313
  ...prefixTagsOf(node).map((t) => `#${t}`),
166187
166314
  ...members.filter((m) => m.$type === "MetadataAnnotation").map((m) => {
@@ -166192,8 +166319,15 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166192
166319
  })
166193
166320
  ];
166194
166321
  add("metadata", metaBadges.map((text) => ({ text })));
166195
- add("variants", withMod("variant").map((m) => item(m)));
166196
- add("variant config", members.filter((m) => m.allVariants === true).map((m) => item(m, `${nameOf2(m) ?? ""} = all ${(m.value?.$cstNode?.text ?? "").trim()}`.trim())));
166322
+ add("variants", [
166323
+ ...members.filter((m) => isVariantMember(m) && nameOf2(m)).map((m) => item(m)),
166324
+ ...inheritedRows((member) => isVariantMember(member) && !!nameOf2(member))
166325
+ ]);
166326
+ const variantElementText = (member) => `${nameOf2(member) ?? ""} = all ${(member.value?.$cstNode?.text ?? "").trim()}`.trim();
166327
+ add("variant element usages", [
166328
+ ...members.filter(isVariantConfigMember).map((m) => item(m, variantElementText(m))),
166329
+ ...inheritedRows(isVariantConfigMember, variantElementText)
166330
+ ]);
166197
166331
  add("satisfies", members.filter((m) => m.$type === "SatisfyStmt").map((m) => item(m, String(m.target ?? ""))));
166198
166332
  return out.length ? out : void 0;
166199
166333
  }
@@ -166811,7 +166945,7 @@ function symbolNameFor(node) {
166811
166945
  const redefined = redefinitionTargets(node)[0];
166812
166946
  return redefined ? `:>> ${lastNameSegment(redefined)}` : void 0;
166813
166947
  }
166814
- function isDefinitionNode(node) {
166948
+ function isDefinitionNode2(node) {
166815
166949
  return node.isDef === true;
166816
166950
  }
166817
166951
  function isInheritableFeature(node) {
@@ -166951,7 +167085,7 @@ function findNameLeaf(node, name) {
166951
167085
  return void 0;
166952
167086
  }
166953
167087
  function inheritedFeatureSymbols(node, inheritedRange, context, seen = /* @__PURE__ */ new Set()) {
166954
- if (!isDefinitionNode(node) || seen.has(node))
167088
+ if (!isDefinitionNode2(node) || seen.has(node))
166955
167089
  return [];
166956
167090
  seen.add(node);
166957
167091
  const symbols = [];
@@ -170579,6 +170713,26 @@ function actionOnlyConstruct(node) {
170579
170713
  const keyword = CONTROL_NODE_KEYWORDS[node.$type];
170580
170714
  return keyword ? { construct: `A '${keyword}' control node`, clause: "7.17.3" } : void 0;
170581
170715
  }
170716
+ function portionKindOf2(node) {
170717
+ const mods = node.modifiers;
170718
+ if (!Array.isArray(mods))
170719
+ return void 0;
170720
+ return mods.find((m) => m === "timeslice" || m === "snapshot");
170721
+ }
170722
+ var NAMESPACE_ONLY_TYPES = /* @__PURE__ */ new Set(["Package", "Document", "NamespaceDecl"]);
170723
+ function owningTypeOf(node) {
170724
+ let owner = node.$container;
170725
+ while (owner && (owner.$type === "PrefixMetadataMember" || owner.$type === "ThenActionMember")) {
170726
+ owner = owner.$container;
170727
+ }
170728
+ if (!owner)
170729
+ return void 0;
170730
+ return NAMESPACE_ONLY_TYPES.has(owner.$type) ? void 0 : owner;
170731
+ }
170732
+ function declLabel(node, kind) {
170733
+ const name = node.name;
170734
+ return name ? `'${name}'` : `this '${kind}'`;
170735
+ }
170582
170736
  var SysmlValidator = class _SysmlValidator {
170583
170737
  indexManager;
170584
170738
  langiumDocuments;
@@ -170802,6 +170956,7 @@ var SysmlValidator = class _SysmlValidator {
170802
170956
  this.checkLanguageDisjointness(node, accept);
170803
170957
  this.checkKermlWellFormedness(node, accept);
170804
170958
  this.checkFilterExpressions(node, accept);
170959
+ this.checkOccurrencePortions(node, accept);
170805
170960
  this.checkCrossReferences(node, accept);
170806
170961
  this.checkAutoImportSuggestions(node, accept);
170807
170962
  this.checkUnits(node, accept);
@@ -171685,6 +171840,49 @@ ${baseIndent}}`;
171685
171840
  this.checkTypeComposition(decl, index2, accept);
171686
171841
  }
171687
171842
  }
171843
+ // ══════════════════════════════════════════════════════════════════════
171844
+ // REQ-160 — occurrence portions: `timeslice` / `snapshot` well-formedness
171845
+ // ══════════════════════════════════════════════════════════════════════
171846
+ // The declaration kinds that are NOT occurrences, so a portion of their life is
171847
+ // meaningless. Kept as a deny-list rather than an allow-list because nearly every
171848
+ // SysML usage kind IS an OccurrenceUsage (part, item, action, state, port,
171849
+ // connection, case, …) while the data-valued ones are few and closed: an
171850
+ // AttributeUsage is typed by a DataType and an enumeration is a special attribute,
171851
+ // neither of which has a life to carve up.
171852
+ static NON_OCCURRENCE_OWNER_TYPES = /* @__PURE__ */ new Set(["AttributeDecl", "EnumDecl", "EnumValueDecl", "MetadataDecl", "MultiplicityDecl"]);
171853
+ // REQ-160 — OMG SysML v2 §8.2.2.9 (Occurrences) with the Kernel Semantic Library
171854
+ // `Occurrences::TimeSlice` / `Occurrences::Snapshot`:
171855
+ //
171856
+ // SSM015 — a portion is a portion OF something. Its owning type must be an
171857
+ // occurrence; declared at package level (or inside an attribute /
171858
+ // enumeration body) there is no life for it to be a portion of.
171859
+ // SSM016 — `Snapshot :> TimeSlice` with a zero duration: a snapshot is the
171860
+ // occurrence at ONE instant. Every portion of a zero-duration
171861
+ // occurrence is itself zero-duration, so a snapshot may own further
171862
+ // snapshots but never a `timeslice`.
171863
+ //
171864
+ // Both are structural — they read modifiers and owners only — so they hold
171865
+ // without resolving a single cross-reference.
171866
+ checkOccurrencePortions(node, accept) {
171867
+ for (const child of ast_utils_exports.streamAllContents(node)) {
171868
+ const kind = portionKindOf2(child);
171869
+ if (!kind)
171870
+ continue;
171871
+ const label = declLabel(child, kind);
171872
+ const owner = owningTypeOf(child);
171873
+ if (!owner) {
171874
+ accept(severity("SSM015", "error"), `A '${kind}' is a portion of an occurrence's life, so ${label} must be declared inside an occurrence \u2014 not directly in a package.`, { node: child, code: "SSM015" });
171875
+ continue;
171876
+ }
171877
+ if (_SysmlValidator.NON_OCCURRENCE_OWNER_TYPES.has(owner.$type)) {
171878
+ accept(severity("SSM015", "error"), `A '${kind}' is a portion of an occurrence's life, and '${owner.name ?? owner.$type}' is not an occurrence.`, { node: child, code: "SSM015" });
171879
+ continue;
171880
+ }
171881
+ if (kind === "timeslice" && portionKindOf2(owner) === "snapshot") {
171882
+ accept(severity("SSM016", "error"), `A 'snapshot' is a zero-duration timeslice, so ${label} cannot be a timeslice of it \u2014 a portion of an instant is itself a snapshot.`, { node: child, code: "SSM016" });
171883
+ }
171884
+ }
171885
+ }
171688
171886
  // REQ-328 — KSM008: a `filter` membership condition must be Boolean. Only a
171689
171887
  // *definitely* non-Boolean expression is flagged — a string or numeric/quantity
171690
171888
  // literal, or an arithmetic/range binary expression. A bare feature path is left
@@ -176534,6 +176732,7 @@ function readProjectConfig(root4) {
176534
176732
  // ../extension/src/webview/diagram/flow/diagram-rules.ts
176535
176733
  var PORT_GLYPH_SIZE = 20;
176536
176734
  var PORT_GLYPH_HALF = PORT_GLYPH_SIZE / 2;
176735
+ var FEATURE_COMPARTMENT_CLEARANCE = PORT_GLYPH_HALF + 12;
176537
176736
  var ACTION_PIN_GLYPH_SIZE = PORT_GLYPH_SIZE;
176538
176737
  function endpointGlyphSize(endpoint) {
176539
176738
  return endpoint.pin ? ACTION_PIN_GLYPH_SIZE : PORT_GLYPH_SIZE;
@@ -176576,18 +176775,26 @@ var DOCK_TANGENT = {
176576
176775
  left: { x: 0, y: 1 },
176577
176776
  right: { x: 0, y: 1 }
176578
176777
  };
176579
- function portGlyphRect(g, w, h) {
176580
- const base = sidePoint(g.side, g.offset, w, h);
176581
- const t = DOCK_TANGENT[g.side];
176582
- const n2 = DOCK_NORMAL[g.side];
176583
- const shift = g.shift ?? { along: 0, across: 0 };
176584
- const cx = base.x + t.x * shift.along + n2.x * shift.across;
176585
- const cy = base.y + t.y * shift.along + n2.y * shift.across;
176778
+ function portGlyphRect(g, w, h, topReserve = 0) {
176586
176779
  const natural = endpointGlyphSize(g.port);
176587
176780
  const along = g.along ?? natural;
176588
176781
  const toggleRun = g.toggleRun ?? 0;
176589
176782
  const across = g.across ?? (along - toggleRun > natural ? NESTED_PORT_HOST_ACROSS : natural);
176590
176783
  const vertical = g.side === "left" || g.side === "right";
176784
+ const base = sidePoint(g.side, g.offset, w, h, topReserve);
176785
+ const t = DOCK_TANGENT[g.side];
176786
+ const n2 = DOCK_NORMAL[g.side];
176787
+ const shift = g.shift ?? { along: 0, across: 0 };
176788
+ const rawCx = base.x + t.x * shift.along + n2.x * shift.across;
176789
+ const rawCy = base.y + t.y * shift.along + n2.y * shift.across;
176790
+ const run = sideLength(g.side, w, h, topReserve);
176791
+ const alongCentre = vertical ? rawCy : rawCx;
176792
+ const rootAlong = g.rootAlong ?? along;
176793
+ const rootCentre = vertical ? base.y : base.x;
176794
+ const boundedRootCentre = topReserve > 0 && vertical ? run >= rootAlong ? Math.max(topReserve + rootAlong / 2, Math.min(topReserve + run - rootAlong / 2, rootCentre)) : topReserve + run / 2 : rootCentre;
176795
+ const boundedAlong = alongCentre + boundedRootCentre - rootCentre;
176796
+ const cx = vertical ? rawCx : boundedAlong;
176797
+ const cy = vertical ? boundedAlong : rawCy;
176591
176798
  const width = vertical ? across : along;
176592
176799
  const height = vertical ? along : across;
176593
176800
  return {
@@ -176661,18 +176868,40 @@ var DOCK_NORMAL = {
176661
176868
  bottom: { x: 0, y: 1 },
176662
176869
  left: { x: -1, y: 0 }
176663
176870
  };
176664
- function sidePoint(side, offset2, w, h) {
176871
+ function portSideHeight(h, topReserve = 0) {
176872
+ const band = Number.isFinite(topReserve) ? Math.max(0, Math.min(Math.max(0, h), topReserve)) : 0;
176873
+ return Math.max(0, h - band);
176874
+ }
176875
+ function sidePoint(side, offset2, w, h, topReserve = 0) {
176876
+ const top = h - portSideHeight(h, topReserve);
176877
+ const sideH = portSideHeight(h, topReserve);
176665
176878
  switch (side) {
176666
176879
  case "top":
176667
176880
  return { x: w * offset2, y: 0 };
176668
176881
  case "bottom":
176669
176882
  return { x: w * offset2, y: h };
176670
176883
  case "left":
176671
- return { x: 0, y: h * offset2 };
176884
+ return { x: 0, y: top + sideH * offset2 };
176672
176885
  case "right":
176673
- return { x: w, y: h * offset2 };
176886
+ return { x: w, y: top + sideH * offset2 };
176674
176887
  }
176675
176888
  }
176889
+ function nearestPortOwnerPlacement(x, y, w, h, topReserve = 0) {
176890
+ const sideH = portSideHeight(h, topReserve);
176891
+ const top = h - sideH;
176892
+ const clamp3 = (value, lo, hi) => Math.max(lo, Math.min(hi, value));
176893
+ const distanceSq = (px, py, qx, qy) => (px - qx) ** 2 + (py - qy) ** 2;
176894
+ const sideY = clamp3(y, top, h);
176895
+ const offsetY = sideH > 0 ? (sideY - top) / sideH : 0.5;
176896
+ const candidates = [
176897
+ { side: "left", offset: offsetY, distance: distanceSq(x, y, 0, sideY) },
176898
+ { side: "right", offset: offsetY, distance: distanceSq(x, y, w, sideY) },
176899
+ { side: "top", offset: w > 0 ? clamp3(x, 0, w) / w : 0.5, distance: distanceSq(x, y, clamp3(x, 0, w), 0) },
176900
+ { side: "bottom", offset: w > 0 ? clamp3(x, 0, w) / w : 0.5, distance: distanceSq(x, y, clamp3(x, 0, w), h) }
176901
+ ];
176902
+ const best = candidates.reduce((nearest, candidate) => candidate.distance < nearest.distance ? candidate : nearest);
176903
+ return { side: best.side, offset: best.offset };
176904
+ }
176676
176905
  var SIDE_ANCHOR_SEP = ":";
176677
176906
  var CONNECT_POINT_SPACING_DEFAULT = 24;
176678
176907
  var CONNECT_POINT_SPACING_MIN = 8;
@@ -176705,12 +176934,12 @@ function centerOutOffsetOrder(offsets) {
176705
176934
  function sidePointOffsetsForLength(length2, spacing) {
176706
176935
  return sidePointOffsets(connectPointCountForLength(length2, spacing));
176707
176936
  }
176708
- function sideLength(side, w, h) {
176709
- return side === "top" || side === "bottom" ? w : h;
176937
+ function sideLength(side, w, h, topReserve = 0) {
176938
+ return side === "top" || side === "bottom" ? w : portSideHeight(h, topReserve);
176710
176939
  }
176711
176940
  var SENDACCEPT_NOTCH = 14;
176712
- function sideEffectiveLength(shape, side, w, h) {
176713
- const raw = sideLength(side, w, h);
176941
+ function sideEffectiveLength(shape, side, w, h, topReserve = 0) {
176942
+ const raw = sideLength(side, w, h, topReserve);
176714
176943
  switch (shape) {
176715
176944
  case "usecase":
176716
176945
  return raw * 0.82;
@@ -176725,7 +176954,7 @@ function sideEffectiveLength(shape, side, w, h) {
176725
176954
  return raw;
176726
176955
  }
176727
176956
  }
176728
- function sideSurfacePoint(shape, side, offset2, w, h) {
176957
+ function sideSurfacePoint(shape, side, offset2, w, h, topReserve = 0) {
176729
176958
  const cx = w / 2;
176730
176959
  const cy = h / 2;
176731
176960
  switch (shape) {
@@ -176753,22 +176982,22 @@ function sideSurfacePoint(shape, side, offset2, w, h) {
176753
176982
  case "send": {
176754
176983
  if (side === "top" || side === "bottom") return { x: (w - SENDACCEPT_NOTCH) * offset2, y: side === "top" ? 0 : h };
176755
176984
  if (side === "right") return { x: w, y: cy };
176756
- return sidePoint(side, offset2, w, h);
176985
+ return sidePoint(side, offset2, w, h, topReserve);
176757
176986
  }
176758
176987
  case "accept": {
176759
176988
  if (side === "left") return { x: SENDACCEPT_NOTCH, y: cy };
176760
- return sidePoint(side, offset2, w, h);
176989
+ return sidePoint(side, offset2, w, h, topReserve);
176761
176990
  }
176762
176991
  default:
176763
- return sidePoint(side, offset2, w, h);
176992
+ return sidePoint(side, offset2, w, h, topReserve);
176764
176993
  }
176765
176994
  }
176766
176995
  function isPackageEndpoint(shape, keyword) {
176767
176996
  if (shape === "package") return true;
176768
176997
  return (keyword ?? "").trim().toLowerCase() === "package";
176769
176998
  }
176770
- function sidePointOffsetsFor(side, w, h, spacing, connectable, shape) {
176771
- return connectable ? sidePointOffsetsForLength(sideEffectiveLength(shape, side, w, h), spacing) : [0.5];
176999
+ function sidePointOffsetsFor(side, w, h, spacing, connectable, shape, topReserve = 0) {
177000
+ return connectable ? sidePointOffsetsForLength(sideEffectiveLength(shape, side, w, h, topReserve), spacing) : [0.5];
176772
177001
  }
176773
177002
  function sideAnchorHandleId(side, offset2) {
176774
177003
  return Math.abs(offset2 - 0.5) < 1e-6 ? side : `${side}${SIDE_ANCHOR_SEP}${offset2.toFixed(3)}`;
@@ -176809,6 +177038,50 @@ function clampDockSide(encoded, portSide) {
176809
177038
  }
176810
177039
  var PORT_PLUS_GAP = 7;
176811
177040
  var PORT_PLUS_HALF = 4;
177041
+ var NODE_USAGE_CORNER_RADIUS = 14;
177042
+ var NODE_ROUNDED_USAGE_RADIUS = 20;
177043
+ var NODE_ROUNDED_TITLED_RADIUS = 12;
177044
+ function nodeCornerRadius(shape, isDef, height, titled) {
177045
+ if (isDef) return 0;
177046
+ if (shape === "action" || shape === "state") {
177047
+ const r = Math.min(height / 2, NODE_ROUNDED_USAGE_RADIUS);
177048
+ return titled ? Math.min(r, NODE_ROUNDED_TITLED_RADIUS) : r;
177049
+ }
177050
+ return NODE_USAGE_CORNER_RADIUS;
177051
+ }
177052
+ var FRAME_USAGE_CORNER_RADIUS = 12;
177053
+ var CONTAINER_CONTROL_SIZE = 20;
177054
+ var CONTAINER_CONTROL_GAP = 1;
177055
+ var CONTAINER_CONTROLS_RIGHT = 2;
177056
+ function containerControlsInset(cornerRadius) {
177057
+ return Math.max(CONTAINER_CONTROLS_RIGHT, Math.ceil(cornerRadius * (1 - Math.SQRT1_2)));
177058
+ }
177059
+ var CONTAINER_CONTROLS_INSET_MAX = containerControlsInset(NODE_ROUNDED_USAGE_RADIUS);
177060
+ var CONTAINER_TITLE_RIGHT_RESERVE = 2 * CONTAINER_CONTROL_SIZE + CONTAINER_CONTROL_GAP + CONTAINER_CONTROLS_INSET_MAX + 11;
177061
+ var COLLAPSED_CONTAINER_MIN_WIDTH = CONTAINER_TITLE_RIGHT_RESERVE + 48;
177062
+ var CONTAINER_GLYPH_VIEWBOX = 20;
177063
+ var CONTAINER_GLYPH_EXTENT = 11;
177064
+ function containerTitleArea(ownerWidth, controlsVisible) {
177065
+ const width = Math.max(0, ownerWidth - (controlsVisible ? CONTAINER_TITLE_RIGHT_RESERVE : 0));
177066
+ return { centerX: width / 2, width };
177067
+ }
177068
+ var NODE_COMPARTMENT_TOP = 30;
177069
+ var FRAME_TITLE_HEIGHT = NODE_COMPARTMENT_TOP;
177070
+ function containerTitleHeight(isFrame) {
177071
+ return isFrame ? FRAME_TITLE_HEIGHT : NODE_COMPARTMENT_TOP;
177072
+ }
177073
+ var CONTAINER_TITLE_KIND_BASELINE = 14;
177074
+ var CONTAINER_TITLE_NAME_BASELINE = 28;
177075
+ function containerTopReserve(bandHeight, isFrame) {
177076
+ return bandHeight && bandHeight > 0 ? containerTitleHeight(isFrame) + bandHeight : 0;
177077
+ }
177078
+ var COMPARTMENT_TITLE_HEIGHT = 16;
177079
+ var COMPARTMENT_ROW_HEIGHT = 14;
177080
+ var COMPARTMENT_TITLE_BASELINE = 12;
177081
+ var COMPARTMENT_ROW_BASELINE = 26;
177082
+ function compartmentBlockHeight(items) {
177083
+ return COMPARTMENT_TITLE_HEIGHT + items * COMPARTMENT_ROW_HEIGHT;
177084
+ }
176812
177085
  function portLabelPlacement(side, x, y, label, half = PORT_GLYPH_HALF, alongHalf = half) {
176813
177086
  const vertical = side === "left" || side === "right";
176814
177087
  const halfX = vertical ? half : alongHalf;
@@ -176846,18 +177119,20 @@ function edgeRouteKey(e, duplicateIndex = 0) {
176846
177119
  const name = e.name ?? e.label;
176847
177120
  return `${e.kind}:${e.from}->${e.to}${name ? `:${name}` : ""}${duplicateIndex > 0 ? `~${duplicateIndex}` : ""}`;
176848
177121
  }
176849
- var GV_TREE_EDGE_KIND_LIST = ["owningMembership", "composition", "reference", "definedBy"];
177122
+ var GV_TREE_EDGE_KIND_LIST = ["owningMembership", "composition", "portion", "reference", "definedBy"];
176850
177123
  var GV_TREE_EDGE_KINDS = new Set(GV_TREE_EDGE_KIND_LIST);
176851
- var GV_TREE_HIERARCHY_EDGE_KIND_LIST = ["owningMembership", "composition"];
177124
+ var GV_TREE_HIERARCHY_EDGE_KIND_LIST = ["owningMembership", "composition", "portion"];
176852
177125
  var GV_TREE_HIERARCHY_EDGE_KINDS = new Set(GV_TREE_HIERARCHY_EDGE_KIND_LIST);
176853
177126
  function gvModeEdges(model, mode) {
176854
177127
  if (mode === "group") return [];
176855
- return model.edges.some((edge) => edge.kind === "owningMembership") ? model.edges.filter((edge) => edge.kind !== "owningMembership") : model.edges;
177128
+ const packageIds = new Set(model.nodes.filter((node) => node.shape === "package").map((node) => node.id));
177129
+ const drawn = (edge) => edge.kind !== "owningMembership" && !packageIds.has(edge.from) && !packageIds.has(edge.to);
177130
+ return model.edges.every(drawn) ? model.edges : model.edges.filter(drawn);
176856
177131
  }
176857
177132
  function isGvUsageNode(n2) {
176858
177133
  return n2.meta?.gvUsage === true;
176859
177134
  }
176860
- var GV_BOXED_USAGE_COMPARTMENTS = /* @__PURE__ */ new Set(["parts", "occurrences"]);
177135
+ var GV_BOXED_USAGE_COMPARTMENTS = /* @__PURE__ */ new Set(["parts", "occurrences", "timeslices", "snapshots", "individuals"]);
176861
177136
  function stripBoxedUsageCompartments(nodes) {
176862
177137
  let changed = false;
176863
177138
  const out = nodes.map((n2) => {
@@ -176898,7 +177173,8 @@ var GV_CATEGORIES = [
176898
177173
  function gvNodeCategory(node) {
176899
177174
  if (node.shape === "package") return "package";
176900
177175
  if (node.shape === "annotation" || node.shape === "dot") return "annotation";
176901
- const k = node.keyword.toLowerCase().trim().replace(/^(individual|timeslice|snapshot|parallel|variation|variant|abstract|ref|derived)\s+/g, "").replace(/\s+def$/, "").trim();
177176
+ const k = node.keyword.toLowerCase().trim().replace(/^(individual|timeslice|snapshot|parallel|variation|variant|abstract|ref|derived)(\s+|$)/g, "").replace(/\s+def$/, "").trim();
177177
+ if (k === "" || k === "def") return "item";
176902
177178
  const alias = {
176903
177179
  part: "part",
176904
177180
  port: "port",
@@ -176952,9 +177228,19 @@ function twKind(text) {
176952
177228
  function compartmentHeight(node) {
176953
177229
  if (!node.compartments?.length) return 0;
176954
177230
  let h = 0;
176955
- for (const c of node.compartments) h += 16 + c.items.length * 16;
177231
+ for (const c of node.compartments) h += compartmentBlockHeight(c.items.length);
176956
177232
  return h;
176957
177233
  }
177234
+ function featureCompartmentBandHeight(node) {
177235
+ if (node?.meta?.featureCompartmentsVisible !== true || !node.compartments?.length) return 0;
177236
+ return compartmentHeight(node) + FEATURE_COMPARTMENT_CLEARANCE;
177237
+ }
177238
+ function frameFeatureCompartmentHeight(frame2) {
177239
+ return featureCompartmentBandHeight(frame2);
177240
+ }
177241
+ function featureBandTopReserve(data) {
177242
+ return containerTopReserve(data.featureCompartmentHeight, data.frame !== void 0);
177243
+ }
176958
177244
  function titleWidth(node) {
176959
177245
  let t = `${node.name}${node.type ? ` : ${node.type}` : ""}`;
176960
177246
  if (node.multiplicity) t += ` ${node.multiplicity}`;
@@ -177013,10 +177299,11 @@ function nodeSize(node, direction) {
177013
177299
  const sideLabelW = (side) => ph.filter((p) => p.side === side).reduce((m, p) => Math.max(m, tw(p.port.name, 9) + 18), 0);
177014
177300
  const portsW = ph.length ? sideLabelW("left") + sideLabelW("right") + 56 : 0;
177015
177301
  const portRows = Math.max(ph.filter((p) => p.side === "left").length, ph.filter((p) => p.side === "right").length);
177302
+ const titleRightReserve = node.meta?.containerControls === true || node.meta?.internalsHidden === true ? CONTAINER_TITLE_RIGHT_RESERVE : 0;
177016
177303
  const w = Math.max(
177017
177304
  NODE_W,
177018
- titleWidth(node) + 2 * PAD,
177019
- twKind(`\xAB${node.keyword}\xBB`) + 2 * PAD,
177305
+ titleWidth(node) + 2 * PAD + titleRightReserve,
177306
+ twKind(`\xAB${node.keyword}\xBB`) + 2 * PAD + titleRightReserve,
177020
177307
  compartmentWidth(node) + 2 * PAD,
177021
177308
  portsW,
177022
177309
  // issue #108 — an elongated port on the top/bottom edge widens the owner,
@@ -177024,13 +177311,15 @@ function nodeSize(node, direction) {
177024
177311
  portedSideLength(ph, "top"),
177025
177312
  portedSideLength(ph, "bottom")
177026
177313
  );
177027
- const h = Math.max(
177314
+ const featureH = featureCompartmentBandHeight(node);
177315
+ const bodyH = Math.max(
177028
177316
  BOX_H,
177029
- 36 + compartmentHeight(node),
177317
+ featureH > 0 ? 0 : 36 + compartmentHeight(node),
177030
177318
  portRows ? 40 + portRows * 24 + 8 : 0,
177031
177319
  portedSideLength(ph, "left"),
177032
177320
  portedSideLength(ph, "right")
177033
177321
  );
177322
+ const h = bodyH + featureH;
177034
177323
  return { w, h };
177035
177324
  }
177036
177325
  function frameMinSize(f) {
@@ -177038,13 +177327,21 @@ function frameMinSize(f) {
177038
177327
  const sideLabelW = (side) => ports.filter((p) => p.side === side).reduce((m, p) => Math.max(m, tw(p.port.name, 9) + 18), 0);
177039
177328
  const headerW = twKind(`\xAB${f.keyword}\xBB`) + tw(f.label, 11) + 28;
177040
177329
  const portsW = sideLabelW("left") + sideLabelW("right") + 56;
177041
- const w = Math.max(160, headerW, portsW, portedSideLength(ports, "top"), portedSideLength(ports, "bottom"));
177330
+ const featureH = frameFeatureCompartmentHeight(f);
177331
+ const w = Math.max(
177332
+ 160,
177333
+ headerW,
177334
+ portsW,
177335
+ ...featureH > 0 ? [compartmentWidth(f) + 2 * PAD] : [],
177336
+ portedSideLength(ports, "top"),
177337
+ portedSideLength(ports, "bottom")
177338
+ );
177042
177339
  const portRows = Math.max(ports.filter((p) => p.side === "left").length, ports.filter((p) => p.side === "right").length);
177043
177340
  const h = Math.max(
177044
- 58,
177045
- 36 + portRows * 24,
177046
- portedSideLength(ports, "left"),
177047
- portedSideLength(ports, "right")
177341
+ 58 + featureH,
177342
+ 36 + portRows * 24 + featureH,
177343
+ featureH + portedSideLength(ports, "left"),
177344
+ featureH + portedSideLength(ports, "right")
177048
177345
  );
177049
177346
  return { w, h };
177050
177347
  }
@@ -177180,6 +177477,7 @@ function assignPortHandles(ports, overrides) {
177180
177477
  side,
177181
177478
  offset: +(PORT_BAND_START + centre * PORT_BAND_LENGTH).toFixed(4),
177182
177479
  along: alongs[i],
177480
+ rootAlong: alongs[i] ?? endpointGlyphSize(port),
177183
177481
  across: alongs[i] === void 0 ? void 0 : metricsOf(port).across,
177184
177482
  toggleRun: toggleRunOf(port) || void 0
177185
177483
  };
@@ -177211,6 +177509,7 @@ function assignPortHandles(ports, overrides) {
177211
177509
  side: parent.side,
177212
177510
  offset: parent.offset,
177213
177511
  shift: { along: (parent.shift?.along ?? 0) + slot.along, across: slot.across },
177512
+ rootAlong: parent.rootAlong ?? parent.along ?? endpointGlyphSize(parent.port),
177214
177513
  ...hasToggle(kid) ? { along, across, toggleRun: toggleRunOf(kid) } : {}
177215
177514
  };
177216
177515
  nestedHandles.push(handle);
@@ -177220,10 +177519,10 @@ function assignPortHandles(ports, overrides) {
177220
177519
  for (const parent of topHandles) placeChildren(parent);
177221
177520
  return [...topHandles, ...nestedHandles];
177222
177521
  }
177223
- function sizeWithOverride(base, o) {
177522
+ function sizeWithOverride(base, o, minimum = { w: 48, h: 24 }) {
177224
177523
  return {
177225
- w: o?.w !== void 0 ? Math.max(48, o.w) : base.w,
177226
- h: o?.h !== void 0 ? Math.max(24, o.h) : base.h
177524
+ w: o?.w !== void 0 ? Math.max(minimum.w, o.w) : base.w,
177525
+ h: o?.h !== void 0 ? Math.max(minimum.h, o.h) : base.h
177227
177526
  };
177228
177527
  }
177229
177528
  function normalizeIvModel(model) {
@@ -177258,6 +177557,7 @@ function promoteIvLeafPartsToFrames(model) {
177258
177557
  const existing = frames[existingIndex];
177259
177558
  frames[existingIndex] = {
177260
177559
  ...existing,
177560
+ layoutKey: n2.layoutKey ?? existing.layoutKey,
177261
177561
  label: n2.name || existing.label,
177262
177562
  isDef: n2.isDef ?? existing.isDef,
177263
177563
  type: n2.type ?? existing.type,
@@ -177275,6 +177575,7 @@ function promoteIvLeafPartsToFrames(model) {
177275
177575
  if (isAnchor || isLeafUsagePart) {
177276
177576
  frames.push({
177277
177577
  id: n2.id,
177578
+ layoutKey: n2.layoutKey,
177278
177579
  label: n2.name,
177279
177580
  keyword: n2.keyword,
177280
177581
  parent: n2.frame,
@@ -177332,13 +177633,11 @@ function applyCaseDefs(model, show) {
177332
177633
  edges
177333
177634
  };
177334
177635
  }
177335
- function collapseIvModel(model, collapsed) {
177636
+ function collapseIvModel(model, hiddenInternals) {
177336
177637
  const frames = model.frames ?? [];
177337
177638
  if (!frames.length) return model;
177338
177639
  const frameById = new Map(frames.map((f) => [f.id, f]));
177339
- const requested = new Set(
177340
- Object.entries(collapsed).filter(([, c]) => c).map(([id2]) => id2).filter((id2) => frameById.get(id2)?.keyword !== "package" && frameById.get(id2)?.meta?.anchorFrame !== true)
177341
- );
177640
+ const requested = new Set(frames.filter((frame2) => framePresentationEnabled(hiddenInternals, frame2, true) && isCollapsibleFrame("iv", frame2)).map((frame2) => frame2.id));
177342
177641
  if (requested.size === 0) return model;
177343
177642
  const hasRequestedAncestor = (id2) => {
177344
177643
  let p = frameById.get(id2)?.parent;
@@ -177371,6 +177670,7 @@ function collapseIvModel(model, collapsed) {
177371
177670
  if (!f) return [];
177372
177671
  return [{
177373
177672
  id: f.id,
177673
+ layoutKey: f.layoutKey,
177374
177674
  name: f.label,
177375
177675
  keyword: f.keyword,
177376
177676
  isDef: f.isDef ?? false,
@@ -177381,7 +177681,7 @@ function collapseIvModel(model, collapsed) {
177381
177681
  compartments: f.compartments,
177382
177682
  frame: f.parent,
177383
177683
  source: f.source,
177384
- meta: { ...f.meta, ivCollapsed: true }
177684
+ meta: { ...f.meta, internalsHidden: true }
177385
177685
  }];
177386
177686
  });
177387
177687
  return {
@@ -177394,22 +177694,67 @@ function collapseIvModel(model, collapsed) {
177394
177694
  function isCollapsibleActionFrame(frame2) {
177395
177695
  return frame2.meta?.compositeAction === true && frame2.meta?.anchorFrame !== true;
177396
177696
  }
177697
+ function isCollapsibleStateFrame(frame2) {
177698
+ return frame2.meta?.composite === true && frame2.meta?.anchorFrame !== true;
177699
+ }
177397
177700
  function frameStateKey(frame2) {
177398
177701
  return frame2.layoutKey ?? frame2.id;
177399
177702
  }
177703
+ function framePresentationEnabled(current2, frame2, acceptLegacyDisplayId = false, whenAbsent = false) {
177704
+ const key = frameStateKey(frame2);
177705
+ if (Object.prototype.hasOwnProperty.call(current2, key)) return current2[key] === true;
177706
+ if (acceptLegacyDisplayId && key !== frame2.id && Object.prototype.hasOwnProperty.call(current2, frame2.id)) {
177707
+ return current2[frame2.id] === true;
177708
+ }
177709
+ return whenAbsent;
177710
+ }
177400
177711
  function isCollapsibleFrame(kind, frame2) {
177401
177712
  if (frame2.meta?.anchorFrame === true) return false;
177402
177713
  if (kind === "iv") return frame2.keyword !== "package";
177403
177714
  if (kind === "afv") return isCollapsibleActionFrame(frame2);
177715
+ if (kind === "stv") return isCollapsibleStateFrame(frame2);
177404
177716
  return false;
177405
177717
  }
177406
- function collapseActionFrames(model, collapsed) {
177718
+ function isFeatureCompartmentNode(kind, node) {
177719
+ if (node.meta?.internalsHidden === true) return false;
177720
+ if (node.meta?.boundary === true || node.meta?.boundaryBox === true) return false;
177721
+ if (kind === "iv") return node.shape === "box" && /\b(?:part|item)\b/i.test(node.keyword);
177722
+ if (kind === "afv") return node.shape === "action";
177723
+ if (kind === "stv") return node.shape === "state";
177724
+ return false;
177725
+ }
177726
+ function applyFeatureCompartmentVisibility(model, visibleFeatureCompartments, hiddenInternals = {}) {
177727
+ if (!CONTAINER_PRESENTATION_KINDS.has(model.kind)) return model;
177728
+ const legacy = model.kind === "iv";
177729
+ let changed = false;
177730
+ const project = (host, leaf) => {
177731
+ const visible = framePresentationEnabled(visibleFeatureCompartments, host, legacy, leaf);
177732
+ const compartments = visible ? host.compartments : void 0;
177733
+ const internalsHidden = leaf ? framePresentationEnabled(hiddenInternals, host, legacy) : host.meta?.internalsHidden === true;
177734
+ if (compartments === host.compartments && host.meta?.containerControls === true && host.meta?.featureCompartmentsVisible === visible && (!leaf || host.meta?.internalsHidden === internalsHidden)) return host;
177735
+ changed = true;
177736
+ return {
177737
+ ...host,
177738
+ compartments,
177739
+ meta: {
177740
+ ...host.meta,
177741
+ containerControls: true,
177742
+ featureCompartmentsVisible: visible,
177743
+ ...leaf ? { internalsHidden } : {}
177744
+ }
177745
+ };
177746
+ };
177747
+ const frames = (model.frames ?? []).map((frame2) => isCollapsibleFrame(model.kind, frame2) ? project(frame2, false) : frame2);
177748
+ const nodes = model.nodes.map((node) => isFeatureCompartmentNode(model.kind, node) ? project(node, true) : node);
177749
+ return changed ? { ...model, frames, nodes } : model;
177750
+ }
177751
+ var CONTAINER_PRESENTATION_KINDS = /* @__PURE__ */ new Set(["iv", "afv", "stv"]);
177752
+ function collapseActionFrames(model, hiddenInternals, isHost = isCollapsibleActionFrame, collapsedShape = "action") {
177407
177753
  const frames = model.frames ?? [];
177408
177754
  if (!frames.length) return model;
177409
177755
  const frameById = new Map(frames.map((f) => [f.id, f]));
177410
- const on = new Set(Object.entries(collapsed).filter(([, value]) => value).map(([key]) => key));
177411
177756
  const requested = new Set(
177412
- frames.filter((f) => on.has(frameStateKey(f)) && isCollapsibleActionFrame(f)).map((f) => f.id)
177757
+ frames.filter((f) => framePresentationEnabled(hiddenInternals, f) && isHost(f)).map((f) => f.id)
177413
177758
  );
177414
177759
  if (requested.size === 0) return model;
177415
177760
  const rootOf = (id2) => {
@@ -177448,17 +177793,18 @@ function collapseActionFrames(model, collapsed) {
177448
177793
  if (!f) return [];
177449
177794
  return [{
177450
177795
  id: f.id,
177796
+ layoutKey: f.layoutKey,
177451
177797
  name: f.label,
177452
177798
  keyword: f.keyword,
177453
177799
  isDef: f.isDef ?? false,
177454
- shape: "action",
177800
+ shape: collapsedShape,
177455
177801
  type: f.type,
177456
177802
  multiplicity: f.multiplicity,
177457
177803
  ports: f.ports,
177458
177804
  compartments: f.compartments,
177459
177805
  frame: f.parent,
177460
177806
  source: f.source,
177461
- meta: { ...f.meta, actionCollapsed: true }
177807
+ meta: { ...f.meta, internalsHidden: true }
177462
177808
  }];
177463
177809
  });
177464
177810
  const seenEdge = /* @__PURE__ */ new Set();
@@ -177498,8 +177844,13 @@ function modelToFlow(model, opts) {
177498
177844
  model = collapseNestedPorts(model, opts.nestedPorts ?? {});
177499
177845
  if (model.kind === "iv") model = normalizeIvModel(model);
177500
177846
  if (model.kind === "iv" && (model.meta?.overview === true || model.meta?.unified === true)) model = promoteIvLeafPartsToFrames(model);
177501
- if (model.kind === "iv" && opts.ivCompartments) model = collapseIvModel(model, opts.ivCompartments);
177502
- if (model.kind === "afv" && opts.collapsedActions) model = collapseActionFrames(model, opts.collapsedActions);
177847
+ const legacyPresentation = model.kind === "iv" ? opts.ivCompartments : model.kind === "afv" ? opts.collapsedActions : void 0;
177848
+ const hiddenInternals = opts.hiddenInternals ?? legacyPresentation ?? {};
177849
+ const visibleFeatureCompartments = opts.visibleFeatureCompartments ?? legacyPresentation ?? {};
177850
+ model = applyFeatureCompartmentVisibility(model, visibleFeatureCompartments, hiddenInternals);
177851
+ if (model.kind === "iv") model = collapseIvModel(model, hiddenInternals);
177852
+ if (model.kind === "afv") model = collapseActionFrames(model, hiddenInternals);
177853
+ if (model.kind === "stv") model = collapseActionFrames(model, hiddenInternals, isCollapsibleStateFrame, "state");
177503
177854
  if (model.kind === "cv" && opts.subjectBoundary) model = applyCaseBoundary(model);
177504
177855
  const ov = opts.overrides ?? {};
177505
177856
  const nodes = [];
@@ -177528,7 +177879,13 @@ function modelToFlow(model, opts) {
177528
177879
  }
177529
177880
  const orderedFrames = [...frames].sort((a2, b) => frameDepth(a2.id) - frameDepth(b.id));
177530
177881
  for (const f of orderedFrames) {
177531
- const size = sizeWithOverride(frameMinSize(f), ov[f.layoutKey ?? f.id]);
177882
+ const natural = frameMinSize(f);
177883
+ const featureH = frameFeatureCompartmentHeight(f);
177884
+ const size = sizeWithOverride(
177885
+ natural,
177886
+ ov[f.layoutKey ?? f.id],
177887
+ { w: 48, h: featureH > 0 ? natural.h : 24 }
177888
+ );
177532
177889
  nodes.push({
177533
177890
  id: f.id,
177534
177891
  type: "frame",
@@ -177540,6 +177897,8 @@ function modelToFlow(model, opts) {
177540
177897
  ports: assignPortHandles(f.ports, opts.portOverrides),
177541
177898
  w: size.w,
177542
177899
  h: size.h,
177900
+ featureCompartmentHeight: featureH,
177901
+ featureCompartmentMinHeight: featureH > 0 ? natural.h : void 0,
177543
177902
  showPortLabels: opts.showPortLabels,
177544
177903
  showMult: opts.showMult,
177545
177904
  connectPointSpacing,
@@ -177591,8 +177950,16 @@ function modelToFlow(model, opts) {
177591
177950
  for (const n2 of renderModelNodes) {
177592
177951
  const canvasNode = nodeForCanvas(n2);
177593
177952
  const isGeo = model.kind === "gev" && n2.geo !== void 0;
177953
+ const featureH = featureCompartmentBandHeight(n2);
177594
177954
  const base = isGeo ? { w: 24, h: 24 } : nodeSize(canvasNode, opts.direction);
177595
- const size = sizeWithOverride(base, ov[n2.layoutKey ?? n2.id]);
177955
+ const size = sizeWithOverride(
177956
+ base,
177957
+ ov[n2.layoutKey ?? n2.id],
177958
+ {
177959
+ w: canvasNode.meta?.containerControls === true || canvasNode.meta?.internalsHidden === true ? COLLAPSED_CONTAINER_MIN_WIDTH : 48,
177960
+ h: featureH > 0 ? base.h : 24
177961
+ }
177962
+ );
177596
177963
  const parentId = n2.frame ?? (model.kind === "gv" && n2.parent && gvPackageIds.has(n2.parent) ? n2.parent : void 0) ?? (boundary ? boundary.id : void 0);
177597
177964
  nodes.push({
177598
177965
  id: n2.id,
@@ -177605,6 +177972,8 @@ function modelToFlow(model, opts) {
177605
177972
  ports: assignPortHandles(n2.ports, opts.portOverrides),
177606
177973
  w: size.w,
177607
177974
  h: size.h,
177975
+ featureCompartmentHeight: featureH,
177976
+ featureCompartmentMinHeight: featureH > 0 ? base.h : void 0,
177608
177977
  showPortLabels: opts.showPortLabels,
177609
177978
  showMult: opts.showMult,
177610
177979
  connectPointSpacing
@@ -177623,10 +177992,14 @@ function modelToFlow(model, opts) {
177623
177992
  // often nearly the whole visible canvas at typical zoom, so its empty
177624
177993
  // interior must pan the canvas rather than move the part (a click
177625
177994
  // there still selects it — dragHandle only gates the DRAG gesture,
177626
- // never click). Restrict the drag-to-move gesture to the part's own
177627
- // keyword/name label, its one visible "something to grab"; every
177628
- // other diagram kind keeps the existing whole-body drag.
177629
- ...model.kind === "iv" && !isGeo ? { dragHandle: ".dnode-kind, .dnode-label" } : {}
177995
+ // never click). Every other diagram kind keeps the whole-body drag.
177996
+ // User report 2026-08-11 the grabbable area was the SVG title TEXT
177997
+ // alone, which a part with hidden internals could barely be moved by
177998
+ // and which carries no move cursor: `.rf-node-header` is the same
177999
+ // full-width title strip a container frame has, so a part behaves the
178000
+ // same whether it draws its internals or not. The text selectors stay
178001
+ // for the title of a node whose header strip is not rendered.
178002
+ ...model.kind === "iv" && !isGeo ? { dragHandle: ".rf-node-header, .dnode-kind, .dnode-label" } : {}
177630
178003
  });
177631
178004
  }
177632
178005
  if (model.kind === "sv") {
@@ -178104,6 +178477,9 @@ async function elkLayout(elk, graph) {
178104
178477
  }
178105
178478
  var FRAME_PAD_TOP = 36;
178106
178479
  var FRAME_PAD = 22;
178480
+ function frameTopPad(node) {
178481
+ return FRAME_PAD_TOP + frameFeatureCompartmentHeight(node.data.frame);
178482
+ }
178107
178483
  var GV_DOCK_FAMILY_ORDER = /* @__PURE__ */ new Map([
178108
178484
  ["owningMembership", 0],
178109
178485
  ["composition", 1],
@@ -178748,7 +179124,7 @@ function buildElkTree(nodes, direction, kind, tree) {
178748
179124
  ...children2.length ? {
178749
179125
  children: children2.map(toElk),
178750
179126
  layoutOptions: {
178751
- "elk.padding": `[top=${FRAME_PAD_TOP},left=${FRAME_PAD},bottom=${FRAME_PAD},right=${FRAME_PAD}]`,
179127
+ "elk.padding": `[top=${frameTopPad(n2)},left=${FRAME_PAD},bottom=${FRAME_PAD},right=${FRAME_PAD}]`,
178752
179128
  ...minimum,
178753
179129
  // REQ-371 — repeat the selected axis on EVERY compound part.
178754
179130
  // ELK does not reliably inherit a root direction into nested
@@ -178854,12 +179230,12 @@ function arrangeIvHierarchy(nodes, direction) {
178854
179230
  children2.push(node);
178855
179231
  byParent.set(key, children2);
178856
179232
  }
178857
- const arrange = (children2, inset) => {
179233
+ const arrange = (children2, insetFor) => {
178858
179234
  if (!children2.length) return;
178859
179235
  const minX = Math.min(...children2.map((child) => child.position.x));
178860
179236
  const minY = Math.min(...children2.map((child) => child.position.y));
178861
- const baseX = inset ? FRAME_PAD : 0;
178862
- const baseY = inset ? FRAME_PAD_TOP : 0;
179237
+ const baseX = insetFor ? FRAME_PAD : 0;
179238
+ const baseY = insetFor ? frameTopPad(insetFor) : 0;
178863
179239
  for (const child of children2) {
178864
179240
  child.position = {
178865
179241
  x: child.position.x - minX + baseX,
@@ -178938,7 +179314,7 @@ function arrangeIvHierarchy(nodes, direction) {
178938
179314
  frame2.data = { ...frame2.data, w: minimum.w, h: minimum.h };
178939
179315
  const children2 = byParent.get(frame2.id) ?? [];
178940
179316
  if (!children2.length) continue;
178941
- arrange(children2, true);
179317
+ arrange(children2, frame2);
178942
179318
  const maxX = Math.max(...children2.map((child) => child.position.x + (child.width ?? child.data.w)));
178943
179319
  const maxY = Math.max(...children2.map((child) => child.position.y + (child.height ?? child.data.h)));
178944
179320
  const width = Math.max(minimum.w, maxX + FRAME_PAD);
@@ -178947,7 +179323,7 @@ function arrangeIvHierarchy(nodes, direction) {
178947
179323
  frame2.height = height;
178948
179324
  frame2.data = { ...frame2.data, w: width, h: height };
178949
179325
  }
178950
- arrange(byParent.get(void 0) ?? [], false);
179326
+ arrange(byParent.get(void 0) ?? [], void 0);
178951
179327
  }
178952
179328
  function applyElkPositions(node, byId) {
178953
179329
  for (const child of node.children ?? []) {
@@ -179066,7 +179442,13 @@ function nextSideAnchor(counts, offsetsCache, node, side, spacing, demand) {
179066
179442
  let offsets = offsetsCache.get(key);
179067
179443
  if (!offsets) {
179068
179444
  const derived = sidePointOffsetsForLength(
179069
- sideEffectiveLength(node.data.node?.shape, side, node.data.w, node.data.h),
179445
+ sideEffectiveLength(
179446
+ node.data.node?.shape,
179447
+ side,
179448
+ node.data.w,
179449
+ node.data.h,
179450
+ featureBandTopReserve(node.data)
179451
+ ),
179070
179452
  spacing
179071
179453
  );
179072
179454
  const needed = demand?.get(key) ?? 0;
@@ -179078,7 +179460,13 @@ function nextSideAnchor(counts, offsetsCache, node, side, spacing, demand) {
179078
179460
  }
179079
179461
  const offset2 = offsets[i % offsets.length];
179080
179462
  if (i < offsets.length && !sidePointOffsetsForLength(
179081
- sideEffectiveLength(node.data.node?.shape, side, node.data.w, node.data.h),
179463
+ sideEffectiveLength(
179464
+ node.data.node?.shape,
179465
+ side,
179466
+ node.data.w,
179467
+ node.data.h,
179468
+ featureBandTopReserve(node.data)
179469
+ ),
179082
179470
  spacing
179083
179471
  ).some((o) => Math.abs(o - offset2) < 1e-6)) {
179084
179472
  const explicit = node.data.explicitSideAnchors?.[side] ?? [];
@@ -189511,7 +189899,7 @@ function dockPoint(node, handleId, byId, fallback) {
189511
189899
  const ph = node.data.ports?.find((p) => p.port.id === port.portId);
189512
189900
  if (ph) {
189513
189901
  const dockSide = clampDockSide(port.side, ph.side);
189514
- const rect = portGlyphRect(ph, w, h);
189902
+ const rect = portGlyphRect(ph, w, h, featureBandTopReserve(node.data));
189515
189903
  const n2 = DOCK_NORMAL[dockSide];
189516
189904
  const at = portDockAt(rect, ph.side, dockSide);
189517
189905
  return { point: { x: pos.x + at.x, y: pos.y + at.y }, normal: n2 };
@@ -189519,12 +189907,19 @@ function dockPoint(node, handleId, byId, fallback) {
189519
189907
  }
189520
189908
  const structural = node.data.structuralDocks?.find((dock) => dock.id === handleId);
189521
189909
  if (structural) {
189522
- const c = sidePoint(structural.side, structural.offset, w, h);
189910
+ const c = sidePoint(structural.side, structural.offset, w, h, featureBandTopReserve(node.data));
189523
189911
  return { point: { x: pos.x + c.x, y: pos.y + c.y }, normal: DOCK_NORMAL[structural.side] };
189524
189912
  }
189525
189913
  const sideAnchor = parseSideAnchorHandleId(handleId);
189526
189914
  if (sideAnchor) {
189527
- const c = sideSurfacePoint(node.data.node?.shape, sideAnchor.side, sideAnchor.offset, w, h);
189915
+ const c = sideSurfacePoint(
189916
+ node.data.node?.shape,
189917
+ sideAnchor.side,
189918
+ sideAnchor.offset,
189919
+ w,
189920
+ h,
189921
+ featureBandTopReserve(node.data)
189922
+ );
189528
189923
  return { point: { x: pos.x + c.x, y: pos.y + c.y }, normal: DOCK_NORMAL[sideAnchor.side] };
189529
189924
  }
189530
189925
  return { point: fallback };
@@ -189607,7 +190002,9 @@ function paintedRouteForEdge(edge, nodes, byId, edges = [], lineStyle = "orthogo
189607
190002
  return { points: manhattanThroughPoints([sd.point, ...route, td.point], sd.normal), corner: 0 };
189608
190003
  }
189609
190004
  const structural = isStructuralBusEdge(edge);
189610
- const [sp, tp] = fanApart(sd.point, td.point, parallelEdgeOffset(
190005
+ const sp = sd.point;
190006
+ const tp = td.point;
190007
+ const via = fanVia(sp, tp, parallelEdgeOffset(
189611
190008
  edge.id,
189612
190009
  edge.source,
189613
190010
  edge.target,
@@ -189615,6 +190012,9 @@ function paintedRouteForEdge(edge, nodes, byId, edges = [], lineStyle = "orthogo
189615
190012
  edge.targetHandle,
189616
190013
  edges
189617
190014
  ));
190015
+ if (via.length) {
190016
+ return { points: manhattanThroughPoints([sp, ...via, tp], sd.normal), corner: 0 };
190017
+ }
189618
190018
  if (edgeUsesObstacleRouting(edge.data?.kind, lineStyle, structural)) {
189619
190019
  return {
189620
190020
  points: routeOrthogonalAroundRects(sp, tp, obstacleRects(nodes, edge.source, edge.target, byId, scene), {
@@ -189642,7 +190042,7 @@ function geometrySignature(nodes) {
189642
190042
  if (n2.dragging === true || n2.resizing === true) continue;
189643
190043
  out += `${n2.id}:${n2.parentId ?? ""}:${n2.type ?? ""}:${n2.hidden ? 1 : 0}:`;
189644
190044
  out += `${n2.position.x},${n2.position.y},${n2.width ?? n2.measured?.width ?? n2.data.w},${n2.height ?? n2.measured?.height ?? n2.data.h}`;
189645
- out += `:${n2.data.node?.shape ?? ""}:${n2.data.isBoundary ? 1 : 0}`;
190045
+ out += `:${n2.data.node?.shape ?? ""}:${n2.data.isBoundary ? 1 : 0}:${n2.data.featureCompartmentHeight ?? 0}`;
189646
190046
  for (const p of n2.data.ports ?? []) {
189647
190047
  out += `|${p.port.id},${p.side},${p.offset},${p.along ?? ""},${p.shift?.along ?? ""},${p.shift?.across ?? ""}`;
189648
190048
  }
@@ -189762,12 +190162,15 @@ function parallelOffsetMap(edges) {
189762
190162
  const groups = /* @__PURE__ */ new Map();
189763
190163
  for (const edge of edges) {
189764
190164
  if (isPortDocked(edge.sourceHandle) || isPortDocked(edge.targetHandle)) continue;
189765
- const [a2, b] = edge.source <= edge.target ? [edge.source, edge.target] : [edge.target, edge.source];
190165
+ const forward = edge.source <= edge.target;
190166
+ const [a2, b] = forward ? [edge.source, edge.target] : [edge.target, edge.source];
190167
+ const [ha, hb] = forward ? [edge.sourceHandle ?? "", edge.targetHandle ?? ""] : [edge.targetHandle ?? "", edge.sourceHandle ?? ""];
189766
190168
  let byTarget = groups.get(a2);
189767
190169
  if (!byTarget) groups.set(a2, byTarget = /* @__PURE__ */ new Map());
189768
- const group = byTarget.get(b) ?? [];
190170
+ const key = `${b}\0${ha}\0${hb}`;
190171
+ const group = byTarget.get(key) ?? [];
189769
190172
  group.push(edge);
189770
- byTarget.set(b, group);
190173
+ byTarget.set(key, group);
189771
190174
  }
189772
190175
  cached = /* @__PURE__ */ new Map();
189773
190176
  for (const byTarget of groups.values()) {
@@ -189788,14 +190191,14 @@ function parallelEdgeOffset(id2, _source, _target, sourceHandle, targetHandle, e
189788
190191
  if (isPortDocked(sourceHandle) || isPortDocked(targetHandle)) return 0;
189789
190192
  return parallelOffsetMap(edges).get(id2) ?? 0;
189790
190193
  }
189791
- function fanApart(sp, tp, off) {
190194
+ function fanVia(sp, tp, off) {
189792
190195
  const dx = tp.x - sp.x;
189793
190196
  const dy = tp.y - sp.y;
189794
190197
  const len = Math.hypot(dx, dy);
189795
- if (!len || !off) return [sp, tp];
190198
+ if (!len || !off) return [];
189796
190199
  const nx = -dy / len;
189797
190200
  const ny = dx / len;
189798
- return [{ x: sp.x + nx * off, y: sp.y + ny * off }, { x: tp.x + nx * off, y: tp.y + ny * off }];
190201
+ return [{ x: (sp.x + tp.x) / 2 + nx * off, y: (sp.y + tp.y) / 2 + ny * off }];
189799
190202
  }
189800
190203
  var GV_BUS_EDGE_KINDS = /* @__PURE__ */ new Set([
189801
190204
  "owningMembership",
@@ -189845,13 +190248,14 @@ function computeEdgePath(args) {
189845
190248
  live ? { x: live.targetX, y: live.targetY } : nodeCentre(targetNode, byId)
189846
190249
  );
189847
190250
  const route = args.route ?? edge.data?.route;
189848
- const fan = route?.length ? 0 : parallelEdgeOffset(edge.id, edge.source, edge.target, edge.sourceHandle, edge.targetHandle, edges);
189849
- const [sp, tp] = fanApart(sd.point, td.point, fan);
189850
- const anchors = route?.length ? [sp, ...route, tp] : [sp, tp];
190251
+ const sp = sd.point;
190252
+ const tp = td.point;
190253
+ const via = route?.length ? route : fanVia(sp, tp, parallelEdgeOffset(edge.id, edge.source, edge.target, edge.sourceHandle, edge.targetHandle, edges));
190254
+ const anchors = [sp, ...via, tp];
189851
190255
  const common = { sourceX: sp.x, sourceY: sp.y, targetX: tp.x, targetY: tp.y };
189852
190256
  const structural = isStructuralBusEdge(edge);
189853
190257
  const wantsObstacles = edgeUsesObstacleRouting(edge.data?.kind, lineStyle, structural);
189854
- const userRouted = route && route.length > 0 ? lineStyle === "curved" ? { path: smoothPathThroughPoints(anchors), label: pathMidpoint(anchors), points: void 0 } : lineStyle === "straight" ? { path: pointsToPath(anchors), label: pathMidpoint(anchors), points: void 0 } : (() => {
190258
+ const guided = via.length > 0 ? lineStyle === "curved" ? { path: smoothPathThroughPoints(anchors), label: pathMidpoint(anchors), points: void 0 } : lineStyle === "straight" ? { path: pointsToPath(anchors), label: pathMidpoint(anchors), points: void 0 } : (() => {
189855
190259
  const pts = manhattanThroughPoints(anchors, sd.normal);
189856
190260
  return { path: pointsToPath(pts), label: pathMidpoint(pts), points: pts };
189857
190261
  })() : void 0;
@@ -189869,8 +190273,8 @@ function computeEdgePath(args) {
189869
190273
  normal: sd.normal
189870
190274
  };
189871
190275
  }
189872
- const autoRouted = wantsObstacles && !userRouted && !args.dragging ? routedOrthogonalPath({ point: sp, normal: sd.normal }, { point: tp, normal: td.normal }, nodes, edge.source, edge.target) : void 0;
189873
- const routed = userRouted ?? autoRouted;
190276
+ const autoRouted = wantsObstacles && !guided && !args.dragging ? routedOrthogonalPath({ point: sp, normal: sd.normal }, { point: tp, normal: td.normal }, nodes, edge.source, edge.target) : void 0;
190277
+ const routed = guided ?? autoRouted;
189874
190278
  if (routed) {
189875
190279
  return {
189876
190280
  path: routed.path,
@@ -189930,6 +190334,13 @@ function markersFor(kind) {
189930
190334
  case "redefinition":
189931
190335
  case "referenceSubsetting":
189932
190336
  return { end: "url(#tri-hollow)" };
190337
+ // REQ-160 — temporal decomposition (OMG 8.2.3.9): an occurrence's life →
190338
+ // one `timeslice`/`snapshot` PORTION of it. A portion specializes the life
190339
+ // it is part of, so it carries the specialization triangle — at the WHOLE
190340
+ // end, which is where the edge starts, so the marker is a start-oriented
190341
+ // twin of `tri-hollow` rather than the end-oriented one.
190342
+ case "portion":
190343
+ return { start: "url(#tri-hollow-start)" };
189933
190344
  case "flow":
189934
190345
  case "message":
189935
190346
  return { end: "url(#arrow-cyan)" };
@@ -190250,6 +190661,10 @@ var MARKER_DEFS_SVG = [
190250
190661
  `<marker markerUnits="userSpaceOnUse" id="arrow-open-neutral" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0 0L10 5L0 10" fill="none" stroke="${GREY}" stroke-width="1.4"/></marker>`,
190251
190662
  `<marker markerUnits="userSpaceOnUse" id="tri-accent" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0 0L10 5L0 10z" fill="${ACCENT}"/></marker>`,
190252
190663
  `<marker markerUnits="userSpaceOnUse" id="tri-hollow" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="11" markerHeight="11" orient="auto"><path d="M0 0L12 6L0 12z" fill="${BG}" stroke="${GREY}" stroke-width="1.2"/></marker>`,
190664
+ // REQ-160 — the portion (temporal-decomposition) triangle sits at the START of
190665
+ // the edge, at the whole occurrence. Same shape as `tri-hollow`, mirrored so
190666
+ // `orient="auto"` points it back along the line instead of down it.
190667
+ `<marker markerUnits="userSpaceOnUse" id="tri-hollow-start" viewBox="0 0 12 12" refX="1" refY="6" markerWidth="11" markerHeight="11" orient="auto"><path d="M12 0L0 6L12 12z" fill="${BG}" stroke="${GREY}" stroke-width="1.2"/></marker>`,
190253
190668
  `<marker markerUnits="userSpaceOnUse" id="defined-by" viewBox="0 0 18 12" refX="17" refY="6" markerWidth="12" markerHeight="8" orient="auto"><circle cx="3.2" cy="3.6" r="1.6" fill="${GREY}"/><circle cx="3.2" cy="8.4" r="1.6" fill="${GREY}"/><path d="M7 1L17 6L7 11z" fill="${GREY}"/></marker>`,
190254
190669
  `<marker markerUnits="userSpaceOnUse" id="owning-membership" viewBox="0 0 14 14" refX="1" refY="7" markerWidth="12" markerHeight="12" orient="auto"><circle cx="7" cy="7" r="5.2" fill="${BG}" stroke="${GREY}" stroke-width="1.2"/><path d="M3.8 7H10.2M7 3.8V10.2" fill="none" stroke="${GREY}" stroke-width="1.2"/></marker>`,
190255
190670
  `<marker markerUnits="userSpaceOnUse" id="diamond-filled" viewBox="0 0 14 14" refX="1" refY="7" markerWidth="12" markerHeight="12" orient="auto"><path d="M0 7L7 0L14 7L7 14z" fill="${GREY}"/></marker>`,
@@ -190263,6 +190678,7 @@ var KIN_MARKER_DEFS_SVG = [
190263
190678
  `<marker markerUnits="userSpaceOnUse" id="arrow-open-neutral-kin" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0 0L10 5L0 10" fill="none" stroke="${KIN}" stroke-width="1.4"/></marker>`,
190264
190679
  `<marker markerUnits="userSpaceOnUse" id="tri-accent-kin" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0 0L10 5L0 10z" fill="${KIN}"/></marker>`,
190265
190680
  `<marker markerUnits="userSpaceOnUse" id="tri-hollow-kin" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="11" markerHeight="11" orient="auto"><path d="M0 0L12 6L0 12z" fill="${BG}" stroke="${KIN}" stroke-width="1.5"/></marker>`,
190681
+ `<marker markerUnits="userSpaceOnUse" id="tri-hollow-start-kin" viewBox="0 0 12 12" refX="1" refY="6" markerWidth="11" markerHeight="11" orient="auto"><path d="M12 0L0 6L12 12z" fill="${BG}" stroke="${KIN}" stroke-width="1.5"/></marker>`,
190266
190682
  `<marker markerUnits="userSpaceOnUse" id="defined-by-kin" viewBox="0 0 18 12" refX="17" refY="6" markerWidth="12" markerHeight="8" orient="auto"><circle cx="3.2" cy="3.6" r="1.6" fill="${KIN}"/><circle cx="3.2" cy="8.4" r="1.6" fill="${KIN}"/><path d="M7 1L17 6L7 11z" fill="${KIN}"/></marker>`,
190267
190683
  `<marker markerUnits="userSpaceOnUse" id="owning-membership-kin" viewBox="0 0 14 14" refX="1" refY="7" markerWidth="12" markerHeight="12" orient="auto"><circle cx="7" cy="7" r="5.2" fill="${BG}" stroke="${KIN}" stroke-width="1.5"/><path d="M3.8 7H10.2M7 3.8V10.2" fill="none" stroke="${KIN}" stroke-width="1.5"/></marker>`,
190268
190684
  `<marker markerUnits="userSpaceOnUse" id="diamond-filled-kin" viewBox="0 0 14 14" refX="1" refY="7" markerWidth="12" markerHeight="12" orient="auto"><path d="M0 7L7 0L14 7L7 14z" fill="${KIN}"/></marker>`,
@@ -190408,6 +190824,9 @@ body {
190408
190824
  * reading. */
190409
190825
  .dframe-rect { fill: var(--node-surface); fill-opacity: 0.92; stroke: var(--fg-3); stroke-width: 1.2; }
190410
190826
  .dframe-rect.container { fill-opacity: 0.16; }
190827
+ /* REQ-366/386 \u2014 an expanded frame remains translucent around its graphical
190828
+ * internals, but its optional bottom feature list is an opaque readable card. */
190829
+ .dfeature-band-bg { fill: var(--node-surface); }
190411
190830
  .dframe-rect.package.library { stroke-dasharray: 5 3; }
190412
190831
  .dframe-rect.selected { stroke: var(--accent); stroke-width: 2; }
190413
190832
  /* RULE (diagram-rules \u2014 Frame): parallel composite-state region \u2014 dashed border. */
@@ -190452,17 +190871,6 @@ body {
190452
190871
  .dnode-shape.decision { stroke: var(--yellow); }
190453
190872
  /* REQ-006 \u2014 selected-node resize grip */
190454
190873
  .dresize-grip { fill: var(--accent); stroke: var(--bg-1); stroke-width: 1; cursor: nwse-resize; }
190455
- /* REQ-366 \u2014 local IV part presentation control in the upper-right corner.
190456
- Issue #18 \u2014 a bare +/\u2212 glyph, no surrounding box; the rect stays in the
190457
- markup only as an invisible, comfortably-sized click target. */
190458
- .div-part-toggle { cursor: pointer; }
190459
- .div-part-toggle rect { fill: transparent; stroke: none; }
190460
- .div-part-toggle text {
190461
- fill: var(--fg-1); font-family: var(--font-ui); font-size: 16px; font-weight: 700;
190462
- pointer-events: none;
190463
- }
190464
- .div-part-toggle:hover text { fill: var(--accent); }
190465
-
190466
190874
  /* edges (REQ-186) */
190467
190875
  .dlink { stroke: var(--fg-2); stroke-width: 1.2; fill: none; }
190468
190876
  .dlink.flow, .dlink.connect, .dlink.message, .dlink.interface { stroke: var(--cyan); }
@@ -190483,6 +190891,9 @@ body {
190483
190891
  }
190484
190892
  .dlink.binding { stroke: var(--yellow); stroke-width: 2.6; } /* REQ-195 \u2014 "=" binding (double-weight line) */
190485
190893
  .dlink.specialization, .dlink.owningMembership, .dlink.composition, .dlink.association { stroke: var(--fg-2); }
190894
+ /* REQ-160 \u2014 temporal decomposition of an occurrence into timeslices/snapshots
190895
+ (OMG 8.2.3.9): a solid structural line, like the specialization it is. */
190896
+ .dlink.portion { stroke: var(--fg-2); }
190486
190897
  /* REQ-094/190 \u2014 custom bound-reference overlay. A dotted directed line is
190487
190898
  deliberately distinct from solid feature membership and its owner diamond. */
190488
190899
  .dlink.reference { stroke: var(--fg-2); stroke-dasharray: 1 3; stroke-linecap: round; }
@@ -190842,16 +191253,23 @@ body {
190842
191253
  /* REQ-190 \u2014 the direction, General-View mode, and line-style segs are icon-only:
190843
191254
  tighter padding (see issue 146). */
190844
191255
  .seg.seg-dir button, .seg.seg-gvmode button, .seg.seg-line-style button, .seg.seg-gridpreset button,
190845
- .seg.seg-afv-filters button { padding: 2px 6px; }
190846
- /* REQ-196 \u2014 the Action Flow show/hide toggles are independent switches, not a
190847
- one-of-N mode: an OFF one dims and takes a slash, so an engaged filter reads at
190848
- a glance (the same treatment as the General View's quick-filter chips). */
190849
- .seg.seg-afv-filters button:not(.on) { opacity: 0.38; }
190850
- .seg.seg-afv-filters button:not(.on)::after {
191256
+ .seg.seg-visibility button, .seg.seg-presentation button { padding: 2px 6px; }
191257
+ /* REQ-196/200/366/386 \u2014 every visibility/presentation segment shares one
191258
+ treatment: visible is lit; hidden is dimmed and slashed. Semantic icon hues
191259
+ remain visible instead of being replaced by a solid orange active button. */
191260
+ .seg.seg-visibility button.on, .seg.seg-presentation button.on {
191261
+ background: var(--bg-active); color: var(--fg-0);
191262
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg-2) 30%, transparent); }
191263
+ .seg.seg-visibility button:not(.on), .seg.seg-presentation button:not(.on) { opacity: 0.38; }
191264
+ .seg.seg-visibility button:not(.on)::after, .seg.seg-presentation button:not(.on)::after {
190851
191265
  content: ''; position: absolute; left: 4px; right: 4px; top: 50%;
190852
- border-top: 1.2px solid currentColor; transform: rotate(-20deg);
191266
+ border-top: 1.2px solid var(--red, #e07a7a); transform: rotate(-20deg);
190853
191267
  }
190854
- .seg.seg-afv-filters button { position: relative; }
191268
+ .seg.seg-visibility button, .seg.seg-presentation button { position: relative; }
191269
+ .visibility-icon.tone-structure, .presentation-icon.tone-structure { color: var(--cyan); }
191270
+ .visibility-icon.tone-behavior, .presentation-icon.tone-behavior { color: var(--accent); }
191271
+ .visibility-icon.tone-definition, .presentation-icon.tone-definition,
191272
+ .definition-visibility-icon { color: var(--violet); }
190855
191273
  /* issue 156 \u2014 General View quick filters: one icon chip per element category
190856
191274
  (parts always show). A chip stays lit in its element-type colour while its
190857
191275
  category is VISIBLE; hiding one dims and slashes it so an engaged filter is
@@ -191089,6 +191507,12 @@ svg.react-flow__connectionline { z-index: 1; }
191089
191507
  .rf-frame .rf-node-svg { pointer-events: none; }
191090
191508
  .rf-frame .dframe-rect { pointer-events: none; }
191091
191509
  .rf-frame-header { position: absolute; pointer-events: auto; cursor: move; }
191510
+ /* issue #58 / user report 2026-08-11 \u2014 the matching title strip on an
191511
+ * Interconnection View PART node. It is what the node's dragHandle selects, so
191512
+ * a part reads and moves identically whether it draws its internals (a frame) or
191513
+ * has them hidden (a node) \u2014 and shows the same move cursor either way, instead
191514
+ * of React Flow's grab hand over a body that cannot actually be dragged. */
191515
+ .rf-node-header { position: absolute; pointer-events: auto; cursor: move; }
191092
191516
  /* REQ-379 \u2014 the frame's BORDER band is hover-sensitive (the interior stays
191093
191517
  * click-through): hovering it reveals and mounts the frame's side connect points,
191094
191518
  * which are drawn on that same border. Stroke-only hit testing, so nothing but the
@@ -191096,7 +191520,7 @@ svg.react-flow__connectionline { z-index: 1; }
191096
191520
  .dframe-hover-band { stroke-width: 12; pointer-events: stroke; }
191097
191521
  .rf-frame .dport-hit { pointer-events: auto; }
191098
191522
  .rf-frame .react-flow__handle { pointer-events: auto; }
191099
- .rf-frame .div-part-toggle { pointer-events: auto; }
191523
+ .rf-frame .dcontainer-controls { pointer-events: auto; }
191100
191524
  .rf-frame .react-flow__resize-control { pointer-events: auto; }
191101
191525
  .rf-node-svg { display: block; overflow: visible; }
191102
191526
  .react-flow__node.selected { box-shadow: none; }
@@ -191293,12 +191717,29 @@ svg.react-flow__connectionline { z-index: 1; }
191293
191717
  color: var(--fg-0); background: var(--bg-1); border: 1px solid var(--line-soft); border-radius: 3px; padding: 0 4px; white-space: nowrap; }
191294
191718
  .rf-edge-label.mult { background: transparent; border: 0; color: var(--fg-2); padding: 0; }
191295
191719
 
191296
- /* REQ-366 / issues.md #18 \u2014 IV part presentation toggle: a BARE +/\u2212 glyph (no
191297
- * box), with a comfortable invisible click target; highlighted on hover/press. */
191298
- .div-part-toggle { width: 20px; height: 20px; padding: 0; margin: 0; border: 0; background: transparent;
191299
- color: var(--fg-1); font: 700 17px var(--font-ui); line-height: 1; cursor: pointer;
191720
+ /* REQ-366/386 \u2014 two adjacent, independent container presentation controls.
191721
+ * +/\u2212 owns graphical internals; the outlined list owns textual feature
191722
+ * compartments. Their compact hit targets stay interactive over frame chrome.
191723
+ * Both marks are STROKED SVG sized to the whole button (nodes.tsx
191724
+ * ContainerControlGlyph), so nothing here can shift them off centre \u2014 a typed
191725
+ * +/\u2212 character and a bordered box drawn in CSS both did (user report
191726
+ * 2026-08-11). */
191727
+ /* The inset is set inline per shape (nodes.tsx ContainerControls): a rounded
191728
+ * outline needs the pair pulled in from its corner arc, a sharp one does not.
191729
+ * These values are the sharp-corner default. */
191730
+ .dcontainer-controls { position: absolute; right: ${CONTAINER_CONTROLS_RIGHT}px; top: ${CONTAINER_CONTROLS_RIGHT}px; z-index: 8;
191731
+ display: inline-flex; align-items: center; gap: ${CONTAINER_CONTROL_GAP}px; pointer-events: auto; }
191732
+ /* The BUTTON is only a hit target: no plate, no fill, in any state (user
191733
+ * direction 2026-08-11 \u2014 "remove that translucent thing surrounding the box I
191734
+ * just want the hamburger menu"). Neither does STATE live here: the mark itself
191735
+ * carries it \u2014 an empty box or a filled one, a + or a \u2212, at one resting colour
191736
+ * ("at best don't even change color"). Only the pointer feedback is a colour. */
191737
+ .div-part-toggle, .dfeature-toggle { width: ${CONTAINER_CONTROL_SIZE}px; height: ${CONTAINER_CONTROL_SIZE}px;
191738
+ padding: 0; margin: 0; border: 0; background: none; cursor: pointer; color: var(--fg-1);
191300
191739
  display: inline-flex; align-items: center; justify-content: center; }
191301
- .div-part-toggle:hover, .div-part-toggle:active, .div-part-toggle:focus-visible { color: var(--accent); outline: none; }
191740
+ .div-part-toggle svg, .dfeature-toggle svg { display: block; }
191741
+ .div-part-toggle:hover, .div-part-toggle:active, .div-part-toggle:focus-visible,
191742
+ .dfeature-toggle:hover, .dfeature-toggle:active, .dfeature-toggle:focus-visible { color: var(--accent); outline: none; }
191302
191743
 
191303
191744
  /* React Flow chrome theming (controls / minimap / attribution) */
191304
191745
  .react-flow__controls { box-shadow: 0 2px 10px rgba(0,0,0,0.35); }
@@ -191395,7 +191836,7 @@ function buildDiagramSvg(options) {
191395
191836
  const { w, h } = sizeOf(node);
191396
191837
  extend2(p.x, p.y, w, h);
191397
191838
  for (const ph of node.data.ports ?? []) {
191398
- const rect = portGlyphRect(ph, w, h);
191839
+ const rect = portGlyphRect(ph, w, h, featureBandTopReserve(node.data));
191399
191840
  extend2(p.x + rect.x, p.y + rect.y, rect.width, rect.height);
191400
191841
  if (!node.data.showPortLabels || rect.elongated) continue;
191401
191842
  const text = `${ph.port.conjugated ? "~" : ""}${ph.port.name}`;
@@ -191538,7 +191979,7 @@ function tableToCsv(table2, order) {
191538
191979
  // ../extension/src/diagram-sidecar.ts
191539
191980
  var import_diagram_protocol2 = __toESM(require_out());
191540
191981
  var FILE_OVERVIEW_ANCHOR = "~file";
191541
- var SIDECAR_VERSION = 3;
191982
+ var SIDECAR_VERSION = 4;
191542
191983
  function emptySideCar() {
191543
191984
  return { version: SIDECAR_VERSION, anchors: {} };
191544
191985
  }
@@ -191593,6 +192034,20 @@ function dropOrdinalKeys(map3) {
191593
192034
  }
191594
192035
  return Object.keys(kept).length > 0 ? kept : void 0;
191595
192036
  }
192037
+ function enabledEntries(value) {
192038
+ if (!value || typeof value !== "object") return {};
192039
+ return Object.fromEntries(Object.entries(value).filter(([, enabled]) => enabled === true));
192040
+ }
192041
+ function normalizeContainerPresentation(state, kind) {
192042
+ const legacy = state;
192043
+ const coupled = kind === "iv" ? enabledEntries(legacy.ivCompartments) : kind === "afv" ? enabledEntries(legacy.collapsedActions) : {};
192044
+ if (Object.keys(coupled).length > 0) {
192045
+ state.hiddenInternals = { ...coupled, ...state.hiddenInternals ?? {} };
192046
+ state.visibleFeatureCompartments = { ...coupled, ...state.visibleFeatureCompartments ?? {} };
192047
+ }
192048
+ delete legacy.ivCompartments;
192049
+ delete legacy.collapsedActions;
192050
+ }
191596
192051
  function normalizeSideCar(value) {
191597
192052
  if (!value || typeof value !== "object") return emptySideCar();
191598
192053
  const v = value;
@@ -191601,16 +192056,36 @@ function normalizeSideCar(value) {
191601
192056
  if (version >= SIDECAR_VERSION) {
191602
192057
  for (const byKind of Object.values(anchors)) {
191603
192058
  if (!byKind || typeof byKind !== "object") continue;
191604
- for (const state of Object.values(byKind)) {
192059
+ for (const [kind, state] of Object.entries(byKind)) {
191605
192060
  if (!state || typeof state !== "object") continue;
191606
192061
  const dir = sanitizeDirection(state.direction);
191607
192062
  if (dir === void 0) delete state.direction;
191608
192063
  else state.direction = dir;
191609
192064
  normalizeGridFields(state);
192065
+ normalizeContainerPresentation(state, kind);
191610
192066
  }
191611
192067
  }
191612
192068
  return { version, anchors };
191613
192069
  }
192070
+ if (version === 3) {
192071
+ const migrated2 = {};
192072
+ for (const [anchor, byKind] of Object.entries(anchors)) {
192073
+ if (!byKind || typeof byKind !== "object") continue;
192074
+ const nextByKind = {};
192075
+ for (const [kind, raw] of Object.entries(byKind)) {
192076
+ if (!raw || typeof raw !== "object") continue;
192077
+ const state = { ...raw };
192078
+ normalizeGridFields(state);
192079
+ const dir = sanitizeDirection(state.direction);
192080
+ if (dir === void 0) delete state.direction;
192081
+ else state.direction = dir;
192082
+ normalizeContainerPresentation(state, kind);
192083
+ nextByKind[kind] = state;
192084
+ }
192085
+ migrated2[anchor] = nextByKind;
192086
+ }
192087
+ return { version: SIDECAR_VERSION, anchors: migrated2 };
192088
+ }
191614
192089
  if (version === 2) {
191615
192090
  const migrated2 = {};
191616
192091
  for (const [anchor, byKind] of Object.entries(anchors)) {
@@ -191629,6 +192104,7 @@ function normalizeSideCar(value) {
191629
192104
  const ports = dropOrdinalKeys(state.ports);
191630
192105
  if (ports) state.ports = ports;
191631
192106
  else delete state.ports;
192107
+ normalizeContainerPresentation(state, kind);
191632
192108
  nextByKind[kind] = state;
191633
192109
  }
191634
192110
  migrated2[anchor] = nextByKind;
@@ -191643,14 +192119,17 @@ function normalizeSideCar(value) {
191643
192119
  if (!raw || typeof raw !== "object") continue;
191644
192120
  const state = raw;
191645
192121
  normalizeGridFields(state);
192122
+ normalizeContainerPresentation(state, kind);
191646
192123
  const next = {};
191647
192124
  if (state.mode !== void 0) next.mode = state.mode;
191648
192125
  if (state.zoom !== void 0) next.zoom = state.zoom;
191649
192126
  const dir = sanitizeDirection(state.direction);
191650
192127
  if (dir !== void 0) next.direction = dir;
191651
- if (state.ivCompartments !== void 0) next.ivCompartments = state.ivCompartments;
192128
+ if (state.hiddenInternals !== void 0) next.hiddenInternals = state.hiddenInternals;
192129
+ if (state.visibleFeatureCompartments !== void 0) {
192130
+ next.visibleFeatureCompartments = state.visibleFeatureCompartments;
192131
+ }
191652
192132
  if (state.nestedPorts !== void 0) next.nestedPorts = state.nestedPorts;
191653
- if (state.collapsedActions !== void 0) next.collapsedActions = state.collapsedActions;
191654
192133
  if (state.gvMode !== void 0) next.gvMode = state.gvMode;
191655
192134
  if (state.gvFilters !== void 0) next.gvFilters = state.gvFilters;
191656
192135
  if (state.lineStyle !== void 0) next.lineStyle = state.lineStyle;
@@ -191726,6 +192205,7 @@ function edgeKindLabel(kind) {
191726
192205
  if (kind === "composition") return "feature membership";
191727
192206
  if (kind === "owningMembership") return "owning membership";
191728
192207
  if (kind === "definedBy") return "defined by";
192208
+ if (kind === "portion") return "temporal decomposition (portion)";
191729
192209
  return kind;
191730
192210
  }
191731
192211
 
@@ -192374,7 +192854,8 @@ function nodeSidePointOffsets(data, side, connectable) {
192374
192854
  data.h,
192375
192855
  data.connectPointSpacing ?? CONNECT_POINT_SPACING_DEFAULT,
192376
192856
  connectable,
192377
- data.node?.shape
192857
+ data.node?.shape,
192858
+ data.featureCompartmentHeight ?? 0
192378
192859
  );
192379
192860
  const explicit = data.explicitSideAnchors?.[side] ?? [];
192380
192861
  return [.../* @__PURE__ */ new Set([...derived, ...explicit])].sort((a2, b) => a2 - b);
@@ -192405,7 +192886,7 @@ var SIDE_NORMAL = {
192405
192886
  left: { x: -1, y: 0 }
192406
192887
  };
192407
192888
  var clampOffset = (v) => Math.max(0.06, Math.min(0.94, v));
192408
- function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192889
+ function usePortDrag(containerRef, w, h, topReserve, onPortMove, onPortPreview, onSelect) {
192409
192890
  const [dragId, setDragId] = (0, import_react8.useState)(void 0);
192410
192891
  const onPortPointerDown = (portId, e, draggable = true) => {
192411
192892
  if (e.button !== 0) return;
@@ -192422,12 +192903,8 @@ function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192422
192903
  const placement = (cx, cy) => {
192423
192904
  const lx = rect.width ? (cx - rect.left) / rect.width * w : 0;
192424
192905
  const ly = rect.height ? (cy - rect.top) / rect.height * h : 0;
192425
- const dl = lx, dr = w - lx, dt = ly, db = h - ly;
192426
- const m = Math.min(dl, dr, dt, db);
192427
- if (m === dt) return { side: "top", offset: clampOffset(lx / w) };
192428
- if (m === db) return { side: "bottom", offset: clampOffset(lx / w) };
192429
- if (m === dl) return { side: "left", offset: clampOffset(ly / h) };
192430
- return { side: "right", offset: clampOffset(ly / h) };
192906
+ const nearest = nearestPortOwnerPlacement(lx, ly, w, h, topReserve);
192907
+ return { side: nearest.side, offset: clampOffset(nearest.offset) };
192431
192908
  };
192432
192909
  const move = (ev) => {
192433
192910
  if (!moved && Math.hypot(ev.clientX - sx, ev.clientY - sy) < 4) return;
@@ -192450,18 +192927,6 @@ function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192450
192927
  };
192451
192928
  return { dragId, onPortPointerDown };
192452
192929
  }
192453
- function sideXY(side, offset2, w, h) {
192454
- switch (side) {
192455
- case "top":
192456
- return { x: w * offset2, y: 0 };
192457
- case "bottom":
192458
- return { x: w * offset2, y: h };
192459
- case "left":
192460
- return { x: 0, y: h * offset2 };
192461
- case "right":
192462
- return { x: w, y: h * offset2 };
192463
- }
192464
- }
192465
192930
  function DualHandle({ id: id2, side, x, y, kind, active, connectable = true, startable = false, size }) {
192466
192931
  const style2 = size !== void 0 ? { left: x, top: y, width: size, height: size, transform: "translate(-50%,-50%)" } : { left: x, top: y, transform: "translate(-50%,-50%)" };
192467
192932
  const cls = `rf-handle rf-handle-${kind}${active ? " active" : ""}`;
@@ -192512,16 +192977,22 @@ function fitKind(s, availPx) {
192512
192977
  function FullTitle({ full, shown }) {
192513
192978
  return shown === full ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: full });
192514
192979
  }
192515
- function compartmentTops(node, top = 30) {
192980
+ function compartmentTops(node, top = NODE_COMPARTMENT_TOP) {
192516
192981
  const comps = node.compartments ?? [];
192517
192982
  const tops = [];
192518
192983
  let acc = top;
192519
192984
  for (const c of comps) {
192520
192985
  tops.push(acc);
192521
- acc += 16 + c.items.length * 16;
192986
+ acc += compartmentBlockHeight(c.items.length);
192522
192987
  }
192523
192988
  return { tops, comps };
192524
192989
  }
192990
+ function containerBandBottom(node, top) {
192991
+ return top + (node.compartments ?? []).reduce((h, c) => h + compartmentBlockHeight(c.items.length), 0);
192992
+ }
192993
+ function ContainerBandRule({ w, y }) {
192994
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { className: "dcompartment-rule closing", x1: 0, y1: y, x2: w, y2: y });
192995
+ }
192525
192996
  function Compartments({ node, w, top }) {
192526
192997
  const { tops, comps } = compartmentTops(node, top);
192527
192998
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: comps.map((c, ci) => {
@@ -192529,13 +193000,13 @@ function Compartments({ node, w, top }) {
192529
193000
  const title = fitKind(c.title, w - 16);
192530
193001
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { className: "dnode-compartment", children: [
192531
193002
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { className: "dcompartment-rule", x1: 0, y1: cy, x2: w, y2: cy }),
192532
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 8, y: cy + 12, children: [
193003
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 8, y: cy + COMPARTMENT_TITLE_BASELINE, children: [
192533
193004
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: c.title, shown: title }),
192534
193005
  title
192535
193006
  ] }),
192536
193007
  c.items.map((it, i) => {
192537
193008
  const shown = fitText(it.text, w - 20, 10);
192538
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: 12, y: cy + 12 + (i + 1) * 14, style: { fontSize: 10 }, children: [
193009
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: 12, y: cy + COMPARTMENT_ROW_BASELINE + i * COMPARTMENT_ROW_HEIGHT, style: { fontSize: 10 }, children: [
192539
193010
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: it.text, shown }),
192540
193011
  shown
192541
193012
  ] }, i);
@@ -192548,37 +193019,53 @@ function variabilityMark(keyword) {
192548
193019
  if (/^variant\b/.test(keyword)) return " \u25C6";
192549
193020
  return "";
192550
193021
  }
193022
+ function nodeShowsControls(node) {
193023
+ return node.meta?.containerControls === true;
193024
+ }
193025
+ function titleNameLine(name, type, mult, alias, availPx) {
193026
+ const suffixChars = (mult ? mult.length + 1 : 0) + (alias ? alias.length + 9 : 0);
193027
+ const budget = Math.max(6, fitChars(availPx, 11) - suffixChars);
193028
+ let nameShown = name;
193029
+ let typeShown = type;
193030
+ if (type) {
193031
+ if (name.length + 3 + type.length > budget) {
193032
+ const nameMax = Math.max(4, Math.min(name.length, Math.ceil(budget * 0.6)));
193033
+ nameShown = clipText(name, nameMax);
193034
+ typeShown = clipText(type, Math.max(3, budget - nameShown.length - 3));
193035
+ }
193036
+ } else {
193037
+ nameShown = clipText(name, budget);
193038
+ }
193039
+ return {
193040
+ nameShown,
193041
+ typeShown,
193042
+ full: `${name}${type ? ` : ${type}` : ""}`,
193043
+ shown: `${nameShown}${typeShown ? ` : ${typeShown}` : ""}`
193044
+ };
193045
+ }
192551
193046
  function BoxBody({ node, w, h, showMult }) {
192552
- const rx = node.isDef ? 0 : 14;
193047
+ const rx = nodeCornerRadius(node.shape, node.isDef, h, false);
192553
193048
  const cls = node.keyword.startsWith("requirement") ? "req" : "";
192554
- const cx = w / 2;
193049
+ const titleArea = containerTitleArea(w, nodeShowsControls(node));
193050
+ const cx = titleArea.centerX;
192555
193051
  const kindFull = `\xAB${node.keyword}\xBB${variabilityMark(node.keyword)}`;
192556
- const kindShown = fitKind(kindFull, w - 8);
192557
- const avail = Math.max(24, w - 12);
193052
+ const kindShown = fitKind(kindFull, Math.max(24, titleArea.width - 8));
192558
193053
  const mult = showMult && node.multiplicity ? node.multiplicity : void 0;
192559
193054
  const alias = node.meta?.alias ? String(node.meta.alias) : void 0;
192560
- const suffixChars = (mult ? mult.length + 1 : 0) + (alias ? alias.length + 9 : 0);
192561
- const budget = Math.max(6, fitChars(avail, 11) - suffixChars);
192562
- let nameShown = node.name;
192563
- let typeShown = node.type;
192564
- if (node.type) {
192565
- if (node.name.length + 3 + node.type.length > budget) {
192566
- const nameMax = Math.max(4, Math.min(node.name.length, Math.ceil(budget * 0.6)));
192567
- nameShown = clipText(node.name, nameMax);
192568
- typeShown = clipText(node.type, Math.max(3, budget - nameShown.length - 3));
192569
- }
192570
- } else {
192571
- nameShown = clipText(node.name, budget);
192572
- }
192573
- const fullTitle = `${node.name}${node.type ? ` : ${node.type}` : ""}`;
192574
- const shownTitle = `${nameShown}${typeShown ? ` : ${typeShown}` : ""}`;
193055
+ const { nameShown, typeShown, full: fullTitle, shown: shownTitle } = titleNameLine(
193056
+ node.name,
193057
+ node.type,
193058
+ mult,
193059
+ alias,
193060
+ Math.max(24, titleArea.width - 12)
193061
+ );
192575
193062
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
192576
193063
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dnode-rect ${cls}`, x: 0, y: 0, width: w, height: h, rx }),
192577
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: 14, textAnchor: "middle", children: [
193064
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: CONTAINER_TITLE_KIND_BASELINE, textAnchor: "middle", children: [
192578
193065
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192579
193066
  kindShown
192580
193067
  ] }),
192581
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: `dnode-label ${node.abstract ? "abstract" : ""}`, x: cx, y: 28, textAnchor: "middle", children: [
193068
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: `dnode-label ${node.abstract ? "abstract" : ""}`, x: cx, y: CONTAINER_TITLE_NAME_BASELINE, textAnchor: "middle", children: [
192582
193069
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: fullTitle, shown: shownTitle }),
192583
193070
  nameShown,
192584
193071
  typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
@@ -192594,41 +193081,57 @@ function BoxBody({ node, w, h, showMult }) {
192594
193081
  alias
192595
193082
  ] }) : ""
192596
193083
  ] }),
192597
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w })
193084
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w }),
193085
+ nodeShowsControls(node) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: containerBandBottom(node, NODE_COMPARTMENT_TOP) }) : null
192598
193086
  ] });
192599
193087
  }
192600
- function RoundedBody({ node, w, h, cls }) {
192601
- const r = node.isDef ? 0 : Math.min(h / 2, 20);
193088
+ function RoundedBody({ node, w, h, cls, showMult }) {
192602
193089
  const comps = node.compartments ?? [];
193090
+ const r = nodeCornerRadius(node.shape, node.isDef, h, comps.length > 0);
192603
193091
  const composite = node.meta?.composite === true ? " composite" : "";
192604
193092
  const kindFull = `\xAB${node.keyword}\xBB${node.meta?.parallel ? " \u2225" : ""}${variabilityMark(node.keyword)}`;
192605
- const kindShown = fitKind(kindFull, w - 8);
192606
- const nameShown = fitText(node.name, w - 12, 11);
193093
+ const titleArea = containerTitleArea(w, nodeShowsControls(node));
193094
+ const cx = titleArea.centerX;
193095
+ const kindShown = fitKind(kindFull, Math.max(24, titleArea.width - 8));
193096
+ const mult = showMult && node.multiplicity ? node.multiplicity : void 0;
193097
+ const { nameShown, typeShown, full: fullTitle, shown: shownTitle } = titleNameLine(
193098
+ node.name,
193099
+ node.type,
193100
+ mult,
193101
+ void 0,
193102
+ Math.max(24, titleArea.width - 12)
193103
+ );
193104
+ const nameLine = (y) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: cx, y, textAnchor: "middle", children: [
193105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: fullTitle, shown: shownTitle }),
193106
+ nameShown,
193107
+ typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
193108
+ " : ",
193109
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tspan", { className: "dnode-type", children: typeShown })
193110
+ ] }) : "",
193111
+ mult ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tspan", { className: "dnode-kind", children: [
193112
+ " ",
193113
+ mult
193114
+ ] }) : ""
193115
+ ] });
192607
193116
  if (comps.length > 0) {
192608
- const cx = w / 2;
192609
193117
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
192610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dnode-rect ${cls}${composite}`, x: 0, y: 0, width: w, height: h, rx: Math.min(r, 12) }),
192611
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: 14, textAnchor: "middle", children: [
193118
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dnode-rect ${cls}${composite}`, x: 0, y: 0, width: w, height: h, rx: r }),
193119
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: CONTAINER_TITLE_KIND_BASELINE, textAnchor: "middle", children: [
192612
193120
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192613
193121
  kindShown
192614
193122
  ] }),
192615
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: cx, y: 28, textAnchor: "middle", children: [
192616
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: node.name, shown: nameShown }),
192617
- nameShown
192618
- ] }),
192619
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w })
193123
+ nameLine(CONTAINER_TITLE_NAME_BASELINE),
193124
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w }),
193125
+ nodeShowsControls(node) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: containerBandBottom(node, NODE_COMPARTMENT_TOP) }) : null
192620
193126
  ] });
192621
193127
  }
192622
193128
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
192623
193129
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dnode-rect ${cls}${composite}`, x: 0, y: 0, width: w, height: h, rx: r }),
192624
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: w / 2, y: h / 2 - 2, textAnchor: "middle", children: [
192625
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: node.name, shown: nameShown }),
192626
- nameShown
192627
- ] }),
192628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: w / 2, y: h / 2 + 12, textAnchor: "middle", children: [
193130
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: h / 2 - 4, textAnchor: "middle", children: [
192629
193131
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192630
193132
  kindShown
192631
- ] })
193133
+ ] }),
193134
+ nameLine(h / 2 + 11)
192632
193135
  ] });
192633
193136
  }
192634
193137
  function UseCaseBody({ node, w, h }) {
@@ -192809,8 +193312,8 @@ function LifelineBody({ node, w, lineBottom, activation, eventMarks }) {
192809
193312
  ] })
192810
193313
  ] });
192811
193314
  }
192812
- function PortGlyph({ ph, w, h, showLabels, selected: selected2, hovered, moving, connectTarget, connectStart, onSelect, onContext }) {
192813
- const rect = portGlyphRect(ph, w, h);
193315
+ function PortGlyph({ ph, w, h, topReserve = 0, showLabels, selected: selected2, hovered, moving, connectTarget, connectStart, onSelect, onContext }) {
193316
+ const rect = portGlyphRect(ph, w, h, topReserve);
192814
193317
  const x = rect.cx;
192815
193318
  const y = rect.cy;
192816
193319
  const half = rect.across / 2;
@@ -192930,12 +193433,12 @@ function PortGlyph({ ph, w, h, showLabels, selected: selected2, hovered, moving,
192930
193433
  }
192931
193434
  );
192932
193435
  }
192933
- function NestedPortToggles({ ports, w, h, onToggle }) {
193436
+ function NestedPortToggles({ ports, w, h, topReserve = 0, onToggle }) {
192934
193437
  const hosts = ports.filter((ph) => ph.port.meta?.nestedHost === true);
192935
193438
  if (!hosts.length) return null;
192936
193439
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: hosts.map((ph) => {
192937
193440
  const expanded2 = ph.port.meta?.nestedExpanded === true;
192938
- const at = nestedPortTogglePoint(portGlyphRect(ph, w, h), ph.side);
193441
+ const at = nestedPortTogglePoint(portGlyphRect(ph, w, h, topReserve), ph.side);
192939
193442
  const what = ph.port.pin ? "parameters" : "ports";
192940
193443
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
192941
193444
  "button",
@@ -192966,6 +193469,84 @@ function NestedPortToggles({ ports, w, h, onToggle }) {
192966
193469
  );
192967
193470
  }) });
192968
193471
  }
193472
+ function ContainerControlGlyph({ kind, expanded: expanded2 }) {
193473
+ const c = CONTAINER_GLYPH_VIEWBOX / 2;
193474
+ const half = CONTAINER_GLYPH_EXTENT / 2;
193475
+ const box = CONTAINER_GLYPH_EXTENT;
193476
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193477
+ "svg",
193478
+ {
193479
+ className: kind === "internals" ? "internals-glyph" : "feature-list-glyph",
193480
+ viewBox: `0 0 ${CONTAINER_GLYPH_VIEWBOX} ${CONTAINER_GLYPH_VIEWBOX}`,
193481
+ width: CONTAINER_GLYPH_VIEWBOX,
193482
+ height: CONTAINER_GLYPH_VIEWBOX,
193483
+ "aria-hidden": "true",
193484
+ children: kind === "internals" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [
193485
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c - half, y1: c, x2: c + half, y2: c }),
193486
+ expanded2 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c, y1: c - half, x2: c, y2: c + half })
193487
+ ] }) : (
193488
+ // A compartment box carrying its rows while the list is HIDDEN and
193489
+ // empty once it is shown (user direction 2026-08-11, correcting the
193490
+ // first reading: "in collapsed state show hamburger menu"). Like
193491
+ // the `+` beside it, the mark advertises what pressing it reveals,
193492
+ // so no colour has to carry the state. The rows are spaced evenly
193493
+ // about the centre line, so the glyph reads as centred either way.
193494
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { stroke: "currentColor", fill: "none", children: [
193495
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: c - half, y: c - half, width: box, height: box, rx: 1.5, strokeWidth: "1.4" }),
193496
+ expanded2 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { strokeWidth: "1.2", strokeLinecap: "round", children: [
193497
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c - half + 2.5, y1: c - 1.6, x2: c + half - 2.5, y2: c - 1.6 }),
193498
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c - half + 2.5, y1: c + 1.6, x2: c + half - 2.5, y2: c + 1.6 })
193499
+ ] })
193500
+ ] })
193501
+ )
193502
+ }
193503
+ );
193504
+ }
193505
+ function ContainerControls({
193506
+ id: id2,
193507
+ label,
193508
+ internalNoun,
193509
+ internalsVisible,
193510
+ featureCompartmentsVisible,
193511
+ cornerRadius,
193512
+ onToggleInternals,
193513
+ onToggleFeatureCompartments
193514
+ }) {
193515
+ if (!onToggleInternals && !onToggleFeatureCompartments) return null;
193516
+ const inset = containerControlsInset(cornerRadius);
193517
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dcontainer-controls nodrag", style: { right: inset, top: inset }, children: [
193518
+ onToggleFeatureCompartments ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193519
+ "button",
193520
+ {
193521
+ className: "dfeature-toggle nodrag",
193522
+ title: `${featureCompartmentsVisible ? "Hide" : "Show"} feature compartments`,
193523
+ "aria-label": `${featureCompartmentsVisible ? "Hide" : "Show"} feature compartments of ${label}`,
193524
+ "aria-pressed": featureCompartmentsVisible,
193525
+ onPointerDown: (e) => e.stopPropagation(),
193526
+ onClick: (e) => {
193527
+ e.stopPropagation();
193528
+ onToggleFeatureCompartments(id2);
193529
+ },
193530
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerControlGlyph, { kind: "featureList", expanded: featureCompartmentsVisible })
193531
+ }
193532
+ ) : null,
193533
+ onToggleInternals ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193534
+ "button",
193535
+ {
193536
+ className: "div-part-toggle nodrag",
193537
+ title: `${internalsVisible ? "Hide" : "Show"} ${internalNoun}`,
193538
+ "aria-label": `${internalsVisible ? "Hide" : "Show"} ${internalNoun} of ${label}`,
193539
+ "aria-pressed": internalsVisible,
193540
+ onPointerDown: (e) => e.stopPropagation(),
193541
+ onClick: (e) => {
193542
+ e.stopPropagation();
193543
+ onToggleInternals(id2);
193544
+ },
193545
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerControlGlyph, { kind: "internals", expanded: internalsVisible })
193546
+ }
193547
+ ) : null
193548
+ ] });
193549
+ }
192969
193550
  function InlineEditor({ initial, mode, elementKind, onCommit, onCancel, w }) {
192970
193551
  const ref = (0, import_react8.useRef)(null);
192971
193552
  (0, import_react8.useEffect)(() => {
@@ -193011,13 +193592,18 @@ var BAND_HEADER_PX = 26;
193011
193592
  var BAND_MIN_W2 = 200;
193012
193593
  var BAND_MIN_H2 = 80;
193013
193594
  var SMALL_GLYPH_SHAPES = /* @__PURE__ */ new Set(["initial", "final", "terminate", "dot", "fork", "join", "decision", "merge"]);
193595
+ var INTERNAL_NOUN = {
193596
+ iv: "internal parts",
193597
+ afv: "nested actions",
193598
+ stv: "sub-states"
193599
+ };
193014
193600
  function shapeBody(node, data) {
193015
193601
  const { w, h, showMult } = data;
193016
193602
  switch (node.shape) {
193017
193603
  case "state":
193018
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "state" });
193604
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "state", showMult });
193019
193605
  case "action":
193020
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "action" });
193606
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "action", showMult });
193021
193607
  case "initial":
193022
193608
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InitialBody, { w, h });
193023
193609
  case "final":
@@ -193067,6 +193653,7 @@ function NodeBody({ node, data, selectedPortId, hoveredPortId, movingPortId, con
193067
193653
  ph,
193068
193654
  w,
193069
193655
  h,
193656
+ topReserve: featureBandTopReserve(data),
193070
193657
  showLabels: data.showPortLabels,
193071
193658
  selected: selectedPortId === ph.port.id,
193072
193659
  hovered: hoveredPortId === ph.port.id,
@@ -193096,15 +193683,26 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193096
193683
  const dim = useInteraction(ctx.store, (s) => s.matchedIds ? !s.matchedIds.has(id2) : false);
193097
193684
  const editing = useInteraction(ctx.store, (s) => s.editing?.nodeId === id2 && !s.editing?.at && !s.editing?.edgeId ? s.editing : void 0);
193098
193685
  const editingHere = editing !== void 0;
193099
- const collapsed = (node.meta?.ivCollapsed === true || node.meta?.actionCollapsed === true) && ctx.onToggleContainer;
193100
- const collapsedWhat = node.meta?.actionCollapsed === true ? "flow" : "internals";
193686
+ const hiddenContainer = node.meta?.internalsHidden === true;
193687
+ const showsControls = nodeShowsControls(node);
193688
+ const internalNoun = INTERNAL_NOUN[data.kind] ?? "internal parts";
193689
+ const featureCompartmentsVisible = node.meta?.featureCompartmentsVisible === true;
193690
+ const featureCompartmentHeight = featureBandTopReserve(data);
193101
193691
  const containerRef = (0, import_react8.useRef)(null);
193102
193692
  const portSource = (pid) => {
193103
193693
  const p = node.ports?.find((pp) => pp.id === pid);
193104
193694
  ctx.onPortSelected?.();
193105
193695
  ctx.onReveal(p?.source, pid);
193106
193696
  };
193107
- const { dragId, onPortPointerDown } = usePortDrag(containerRef, w, h, ctx.onPortMove, ctx.onPortPreview, portSource);
193697
+ const { dragId, onPortPointerDown } = usePortDrag(
193698
+ containerRef,
193699
+ w,
193700
+ h,
193701
+ featureCompartmentHeight,
193702
+ ctx.onPortMove,
193703
+ ctx.onPortPreview,
193704
+ portSource
193705
+ );
193108
193706
  const ports = data.ports;
193109
193707
  const liveData = { ...data, w, h, ports };
193110
193708
  const directRelation = directRelationForHandle(data.kind, {
@@ -193144,7 +193742,9 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193144
193742
  children: [
193145
193743
  resizable ? (() => {
193146
193744
  const min2 = resizeMinFor(node.shape, h);
193147
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NodeResizer, { minWidth: min2.minWidth, minHeight: min2.minHeight, maxHeight: min2.maxHeight, isVisible: true, color: "var(--accent)" });
193745
+ const minWidth = showsControls ? Math.max(min2.minWidth, COLLAPSED_CONTAINER_MIN_WIDTH) : min2.minWidth;
193746
+ const minHeight = Math.max(min2.minHeight, data.featureCompartmentMinHeight ?? 0);
193747
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NodeResizer, { minWidth, minHeight, maxHeight: min2.maxHeight, isVisible: true, color: "var(--accent)" });
193148
193748
  })() : null,
193149
193749
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193150
193750
  NodeBody,
@@ -193160,12 +193760,25 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193160
193760
  onPortContext: ctx.onContextNode
193161
193761
  }
193162
193762
  ),
193763
+ data.kind === "iv" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193764
+ "div",
193765
+ {
193766
+ className: "rf-node-header",
193767
+ title: node.name,
193768
+ style: {
193769
+ left: 0,
193770
+ top: 0,
193771
+ height: NODE_COMPARTMENT_TOP,
193772
+ width: containerTitleArea(w, nodeShowsControls(node)).width
193773
+ }
193774
+ }
193775
+ ) : null,
193163
193776
  SIDES.flatMap((side, si) => {
193164
193777
  const n2 = SIDE_NORMAL[side];
193165
193778
  const out = SMALL_GLYPH_SHAPES.has(node.shape) ? 9 : 0;
193166
193779
  const offsets = sideOffsets[si];
193167
193780
  return offsets.map((offset2, i) => {
193168
- const { x, y } = sideSurfacePoint(node.shape, side, offset2, w, h);
193781
+ const { x, y } = sideSurfacePoint(node.shape, side, offset2, w, h, featureCompartmentHeight);
193169
193782
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193170
193783
  DualHandle,
193171
193784
  {
@@ -193182,7 +193795,7 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193182
193795
  });
193183
193796
  }),
193184
193797
  ports.flatMap((ph) => {
193185
- const rect = portGlyphRect(ph, w, h);
193798
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193186
193799
  const c = { x: rect.cx, y: rect.cy };
193187
193800
  const startable = directRelationForHandle(data.kind, {
193188
193801
  id: ph.port.id,
@@ -193203,7 +193816,7 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193203
193816
  ];
193204
193817
  }),
193205
193818
  ctx.onPortMove ? ports.map((ph) => {
193206
- const rect = portGlyphRect(ph, w, h);
193819
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193207
193820
  const nestedChild = ph.port.parentPort !== void 0;
193208
193821
  const strip = portInteractionStrip(rect, ph.side);
193209
193822
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193237,20 +193850,27 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193237
193850
  }
193238
193851
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: handles });
193239
193852
  })() : null,
193240
- ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NestedPortToggles, { ports, w, h, onToggle: ctx.onToggleNestedPort }) : null,
193241
- collapsed ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193242
- "button",
193853
+ ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193854
+ NestedPortToggles,
193243
193855
  {
193244
- className: "div-part-toggle nodrag",
193245
- title: collapsedWhat === "flow" ? "Show the actions inside this one" : "Show internal parts and connections",
193246
- "aria-label": `Show ${collapsedWhat} of ${node.name}`,
193247
- style: { position: "absolute", right: 2, top: 2 },
193248
- onPointerDown: (e) => e.stopPropagation(),
193249
- onClick: (e) => {
193250
- e.stopPropagation();
193251
- ctx.onToggleContainer(id2);
193252
- },
193253
- children: "+"
193856
+ ports,
193857
+ w,
193858
+ h,
193859
+ topReserve: featureCompartmentHeight,
193860
+ onToggle: ctx.onToggleNestedPort
193861
+ }
193862
+ ) : null,
193863
+ showsControls ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193864
+ ContainerControls,
193865
+ {
193866
+ id: id2,
193867
+ label: node.name,
193868
+ internalNoun,
193869
+ internalsVisible: !hiddenContainer,
193870
+ featureCompartmentsVisible,
193871
+ cornerRadius: nodeCornerRadius(node.shape, node.isDef, h, (node.compartments?.length ?? 0) > 0),
193872
+ onToggleInternals: ctx.onToggleInternals,
193873
+ onToggleFeatureCompartments: ctx.onToggleFeatureCompartments
193254
193874
  }
193255
193875
  ) : null,
193256
193876
  editingHere && ctx.onEditCommit && ctx.onEditCancel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193286,6 +193906,23 @@ function packageFramePath(w, h) {
193286
193906
  "Z"
193287
193907
  ].join(" ");
193288
193908
  }
193909
+ function frameFeatureBandPath(w, h, bottom, radius) {
193910
+ const left = 1;
193911
+ const right = Math.max(left, w - 1);
193912
+ const top = 1;
193913
+ const end = Math.max(top, Math.min(bottom, h - 1));
193914
+ const r = Math.max(0, Math.min(radius, (right - left) / 2, end - top));
193915
+ if (r === 0) return `M ${left} ${top} H ${right} V ${end} H ${left} Z`;
193916
+ return [
193917
+ `M ${left} ${top + r}`,
193918
+ `Q ${left} ${top} ${left + r} ${top}`,
193919
+ `H ${right - r}`,
193920
+ `Q ${right} ${top} ${right} ${top + r}`,
193921
+ `V ${end}`,
193922
+ `H ${left}`,
193923
+ "Z"
193924
+ ].join(" ");
193925
+ }
193289
193926
  function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193290
193927
  const ctx = useDiagram();
193291
193928
  const frame2 = data.frame;
@@ -193300,13 +193937,30 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193300
193937
  const isPackage2 = frame2.keyword === "package";
193301
193938
  const isLibraryPackage = isPackage2 && frame2.meta?.library === true;
193302
193939
  const resizable = selected2 && (!isPackage2 || data.kind === "gv");
193303
- const collapsible = isCollapsibleFrame(data.kind, frame2) && ctx.onToggleContainer;
193940
+ const collapsible = isCollapsibleFrame(data.kind, frame2);
193941
+ const controllable = collapsible && (ctx.onToggleInternals !== void 0 || ctx.onToggleFeatureCompartments !== void 0);
193942
+ const internalNoun = INTERNAL_NOUN[data.kind] ?? "internal parts";
193943
+ const featureCompartmentsVisible = frame2.meta?.featureCompartmentsVisible === true;
193944
+ const featureBandHeight = data.featureCompartmentHeight ?? frameFeatureCompartmentHeight(frame2);
193945
+ const featureCompartmentHeight = featureBandHeight > 0 ? FRAME_TITLE_HEIGHT + featureBandHeight : 0;
193946
+ const bandBottom = containerBandBottom(
193947
+ featureBandHeight > 0 ? frame2 : { compartments: void 0 },
193948
+ FRAME_TITLE_HEIGHT
193949
+ );
193304
193950
  const containerRef = (0, import_react8.useRef)(null);
193305
193951
  const portSource = (pid) => {
193306
193952
  ctx.onPortSelected?.();
193307
193953
  ctx.onReveal(frame2.ports?.find((p) => p.id === pid)?.source, pid);
193308
193954
  };
193309
- const { dragId, onPortPointerDown } = usePortDrag(containerRef, w, h, ctx.onPortMove, ctx.onPortPreview, portSource);
193955
+ const { dragId, onPortPointerDown } = usePortDrag(
193956
+ containerRef,
193957
+ w,
193958
+ h,
193959
+ featureCompartmentHeight,
193960
+ ctx.onPortMove,
193961
+ ctx.onPortPreview,
193962
+ portSource
193963
+ );
193310
193964
  const ports = data.ports;
193311
193965
  const showPorts = ports.length > 0;
193312
193966
  const isOuterViewFrame = (data.kind === "afv" || data.kind === "stv") && frame2.meta?.anchorFrame === true;
@@ -193345,32 +193999,59 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193345
193999
  ctx.onContextNode(id2, e.clientX, e.clientY);
193346
194000
  },
193347
194001
  children: [
193348
- resizable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NodeResizer, { minWidth: isPackage2 ? 180 : 80, minHeight: isPackage2 ? 100 : 60, isVisible: true, color: "var(--accent)" }) : null,
194002
+ resizable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194003
+ NodeResizer,
194004
+ {
194005
+ minWidth: isPackage2 ? 180 : 80,
194006
+ minHeight: Math.max(isPackage2 ? 100 : 60, data.featureCompartmentMinHeight ?? 0),
194007
+ isVisible: true,
194008
+ color: "var(--accent)"
194009
+ }
194010
+ ) : null,
193349
194011
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { className: "rf-node-svg", width: w, height: h, style: { overflow: "visible" }, children: [
193350
194012
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: "dframe-hover-band", x: 0, y: 0, width: w, height: h, rx: 12, fill: "none", stroke: "transparent" }),
193351
194013
  isPackage2 && data.kind === "gv" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { className: `dframe-rect package${isLibraryPackage ? " library" : ""}${data.isContainer ? " container" : ""}`, d: packageFramePath(w, h) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dframe-rect${data.isContainer ? " container" : ""}${frame2.meta?.boundaryBox === true ? " boundary" : ""}${frame2.meta?.parallel === true ? " parallel" : ""}${frame2.meta?.loop === true ? " loop" : ""}${frame2.meta?.performerLane === true ? " performer" : ""}`, x: 0, y: 0, width: w, height: h, rx: frame2.isDef === true ? 0 : 12 }),
194014
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194015
+ "path",
194016
+ {
194017
+ className: "dfeature-band-bg",
194018
+ d: frameFeatureBandPath(w, h, bandBottom, frame2.isDef === true ? 0 : 12)
194019
+ }
194020
+ ) : null,
193352
194021
  (() => {
193353
194022
  const kindFull = `\xAB${frame2.keyword}\xBB${isLibraryPackage ? " (standard library)" : ""}${frame2.meta?.parallel === true ? " \u2225" : ""}${variabilityMark(frame2.keyword)}`;
193354
- const kindShown = fitKind(kindFull, Math.max(40, w - 40));
193355
- const labelX = 10 + (kindShown.length + 1) * 7;
193356
- const labelShown = fitText(frame2.label, Math.max(24, w - labelX - 10), 11);
194023
+ const avail = Math.max(24, containerTitleArea(w, controllable).width - 14);
194024
+ const kindShown = fitKind(kindFull, avail);
194025
+ const mult = data.showMult && frame2.multiplicity ? frame2.multiplicity : void 0;
194026
+ const title = titleNameLine(frame2.label, frame2.type, mult, void 0, avail);
193357
194027
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
193358
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 10, y: 16, children: [
194028
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 10, y: CONTAINER_TITLE_KIND_BASELINE, children: [
193359
194029
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
193360
194030
  kindShown
193361
194031
  ] }),
193362
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: labelX, y: 16, style: { fontSize: 11 }, children: [
193363
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: frame2.label, shown: labelShown }),
193364
- labelShown
194032
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: 10, y: CONTAINER_TITLE_NAME_BASELINE, style: { fontSize: 11 }, children: [
194033
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: title.full, shown: title.shown }),
194034
+ title.nameShown,
194035
+ title.typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
194036
+ " : ",
194037
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tspan", { className: "dnode-type", children: title.typeShown })
194038
+ ] }) : "",
194039
+ mult ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tspan", { className: "dnode-kind", children: [
194040
+ " ",
194041
+ mult
194042
+ ] }) : ""
193365
194043
  ] })
193366
194044
  ] });
193367
194045
  })(),
194046
+ featureBandHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node: frame2, w, top: FRAME_TITLE_HEIGHT }) : null,
194047
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: bandBottom }) : null,
193368
194048
  ports.map((ph) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193369
194049
  PortGlyph,
193370
194050
  {
193371
194051
  ph,
193372
194052
  w,
193373
194053
  h,
194054
+ topReserve: featureCompartmentHeight,
193374
194055
  showLabels: true,
193375
194056
  selected: selectedPortId === ph.port.id,
193376
194057
  hovered: hoveredPortId === ph.port.id,
@@ -193383,11 +194064,18 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193383
194064
  ph.port.id
193384
194065
  ))
193385
194066
  ] }),
193386
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rf-frame-header", style: { left: 0, top: 0, width: "100%", height: 22 }, title: frame2.label }),
194067
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194068
+ "div",
194069
+ {
194070
+ className: "rf-frame-header",
194071
+ style: { left: 0, top: 0, width: "100%", height: FRAME_TITLE_HEIGHT },
194072
+ title: frame2.label
194073
+ }
194074
+ ),
193387
194075
  SIDES.flatMap((side, si) => {
193388
194076
  const offsets = sideOffsets[si];
193389
194077
  return offsets.map((offset2, i) => {
193390
- const { x, y } = sideXY(side, offset2, w, h);
194078
+ const { x, y } = sidePoint(side, offset2, w, h, featureCompartmentHeight);
193391
194079
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193392
194080
  DualHandle,
193393
194081
  {
@@ -193404,7 +194092,7 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193404
194092
  });
193405
194093
  }),
193406
194094
  showPorts ? ports.flatMap((ph) => {
193407
- const rect = portGlyphRect(ph, w, h);
194095
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193408
194096
  const c = { x: rect.cx, y: rect.cy };
193409
194097
  const startable = directRelationForHandle(data.kind, {
193410
194098
  id: ph.port.id,
@@ -193425,7 +194113,7 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193425
194113
  ];
193426
194114
  }) : null,
193427
194115
  ctx.onPortMove ? ports.map((ph) => {
193428
- const rect = portGlyphRect(ph, w, h);
194116
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193429
194117
  const nestedChild = ph.port.parentPort !== void 0;
193430
194118
  const strip = portInteractionStrip(rect, ph.side);
193431
194119
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193447,20 +194135,27 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193447
194135
  `pd-${ph.port.id}`
193448
194136
  );
193449
194137
  }) : null,
193450
- ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NestedPortToggles, { ports, w, h, onToggle: ctx.onToggleNestedPort }) : null,
194138
+ ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194139
+ NestedPortToggles,
194140
+ {
194141
+ ports,
194142
+ w,
194143
+ h,
194144
+ topReserve: featureCompartmentHeight,
194145
+ onToggle: ctx.onToggleNestedPort
194146
+ }
194147
+ ) : null,
193451
194148
  collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193452
- "button",
194149
+ ContainerControls,
193453
194150
  {
193454
- className: "div-part-toggle nodrag",
193455
- title: data.kind === "afv" ? "Hide the actions inside this one" : "Show compartments",
193456
- "aria-label": data.kind === "afv" ? `Collapse ${frame2.label}` : `Show compartments of ${frame2.label}`,
193457
- style: { position: "absolute", right: 2, top: 2 },
193458
- onPointerDown: (e) => e.stopPropagation(),
193459
- onClick: (e) => {
193460
- e.stopPropagation();
193461
- ctx.onToggleContainer(id2);
193462
- },
193463
- children: "\u2212"
194151
+ id: id2,
194152
+ label: frame2.label,
194153
+ internalNoun,
194154
+ internalsVisible: true,
194155
+ featureCompartmentsVisible,
194156
+ cornerRadius: frame2.isDef === true ? 0 : FRAME_USAGE_CORNER_RADIUS,
194157
+ onToggleInternals: ctx.onToggleInternals,
194158
+ onToggleFeatureCompartments: ctx.onToggleFeatureCompartments
193464
194159
  }
193465
194160
  ) : null,
193466
194161
  editing && ctx.onEditCommit && ctx.onEditCancel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193648,9 +194343,9 @@ function resolveViewConfig(settings, view, overrides = {}) {
193648
194343
  gvFilters: new Set(view?.gvFilters ?? []),
193649
194344
  // Case View definitions are shown unless the user turned them off.
193650
194345
  cvShowDefs: view?.cvShowDefs !== false,
193651
- ivCompartments: view?.ivCompartments ?? {},
194346
+ hiddenInternals: view?.hiddenInternals ?? {},
194347
+ visibleFeatureCompartments: view?.visibleFeatureCompartments ?? {},
193652
194348
  nestedPorts: view?.nestedPorts ?? {},
193653
- collapsedActions: view?.collapsedActions ?? {},
193654
194349
  showPortLabels: settings.showPortLabels,
193655
194350
  showMultiplicities: settings.showMultiplicities,
193656
194351
  showSubjectAsBoundaryBox: settings.showSubjectAsBoundaryBox,
@@ -193725,9 +194420,9 @@ async function renderDiagramSvg(document2, provider, options) {
193725
194420
  showMult: config.showMultiplicities,
193726
194421
  connectPointSpacing: config.connectPointSpacing,
193727
194422
  subjectBoundary: config.showSubjectAsBoundaryBox,
193728
- ivCompartments: config.ivCompartments,
194423
+ hiddenInternals: config.hiddenInternals,
194424
+ visibleFeatureCompartments: config.visibleFeatureCompartments,
193729
194425
  nestedPorts: config.nestedPorts,
193730
- collapsedActions: config.collapsedActions,
193731
194426
  overrides: config.overrides,
193732
194427
  portOverrides: config.portOverrides,
193733
194428
  edgeRoutes: config.edgeRoutes,
@@ -193797,7 +194492,7 @@ async function runExport(command) {
193797
194492
  }
193798
194493
 
193799
194494
  // src/main.ts
193800
- var VERSION2 = true ? "0.14.5" : "dev";
194495
+ var VERSION2 = true ? "0.15.0" : "dev";
193801
194496
  function display(file) {
193802
194497
  const rel2 = path9.relative(process.cwd(), file);
193803
194498
  return rel2 && !rel2.startsWith("..") ? rel2.split(path9.sep).join("/") : file;