sysml-diagram 0.14.5 → 0.15.1

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
@@ -133689,6 +133689,9 @@ function isExhibitStateDecl(item) {
133689
133689
  var ExitAction = "ExitAction";
133690
133690
  var ExposeMember = "ExposeMember";
133691
133691
  var ExposePath = "ExposePath";
133692
+ function isExposePath(item) {
133693
+ return reflection2.isInstance(item, ExposePath);
133694
+ }
133692
133695
  var ExpressionDecl = "ExpressionDecl";
133693
133696
  var FeatureDecl = "FeatureDecl";
133694
133697
  function isFeatureDecl(item) {
@@ -161748,6 +161751,12 @@ function keywordFor(node) {
161748
161751
  const parallel = isParallelState(node) ? "parallel " : "";
161749
161752
  const leadMods = [variability, temporal].filter(Boolean);
161750
161753
  const prefix = leadMods.length ? leadMods.map((m) => `${m} `).join("") : parallel;
161754
+ if (t === "BareDefDecl")
161755
+ return `${prefix}def`.trim();
161756
+ if (t === "FeatureShorthand") {
161757
+ const kinds = (Array.isArray(n2.usageKinds) ? n2.usageKinds : []).join(" ");
161758
+ return `${prefix}${kinds}`.trim() || "feature";
161759
+ }
161751
161760
  if (t === "ActorDecl" || t === "SubjectDecl" || t === "StakeholderDecl")
161752
161761
  return `${prefix}${word}`;
161753
161762
  return def ? `${prefix}${word} def` : `${prefix}${word}`;
@@ -161756,7 +161765,39 @@ var isDefinitionKind = (node) => isPartDecl(node) || isPortDecl(node) || isItemD
161756
161765
  // genuine SysML definitions (FlowConnectionDefinition / AssociationStructure /
161757
161766
  // AllocationDefinition) and belong in the General View like any other def.
161758
161767
  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);
161768
+ var isBareDefDecl = (node) => node.$type === "BareDefDecl";
161769
+ var isDefinitionNode = (node) => isDefinitionKind(node) && node.isDef === true || isBareDefDecl(node);
161770
+ var modifiersOf = (node) => {
161771
+ const mods = node.modifiers;
161772
+ return Array.isArray(mods) ? mods : [];
161773
+ };
161774
+ var portionKindOf = (node) => modifiersOf(node).find((m) => m === "timeslice" || m === "snapshot");
161775
+ var isIndividualOccurrence = (node) => modifiersOf(node).includes("individual");
161776
+ var isOccurrenceModified = (node) => portionKindOf(node) !== void 0 || isIndividualOccurrence(node);
161777
+ var redefinedNameOf = (node) => {
161778
+ const n2 = node;
161779
+ for (const rel2 of [...n2.preRelationships ?? [], ...n2.relationships ?? []]) {
161780
+ if (rel2.kind === ":>>" || rel2.kind === "redefines") {
161781
+ const target = (rel2.targets ?? [])[0];
161782
+ if (target)
161783
+ return lastSeg(target);
161784
+ }
161785
+ }
161786
+ return void 0;
161787
+ };
161788
+ var portionLabelOf = (node) => nameOf2(node) ?? (isOccurrenceModified(node) ? redefinedNameOf(node) : void 0);
161789
+ function diagramNodeId(node) {
161790
+ const name = nameOf2(node);
161791
+ if (name)
161792
+ return qnameOf(node) || name;
161793
+ const label = portionLabelOf(node);
161794
+ if (!label)
161795
+ return void 0;
161796
+ const ownerQname = qnameOf(node);
161797
+ return ownerQname ? `${ownerQname}::${label}` : label;
161798
+ }
161799
+ var isDeclaredEventOccurrence = (m) => isEventDecl(m) && m.isOccurrence === true;
161800
+ 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
161801
  function specializationTargets(node) {
161761
161802
  const n2 = node;
161762
161803
  const out = [];
@@ -162316,7 +162357,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162316
162357
  return false;
162317
162358
  }
162318
162359
  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));
162360
+ 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
162361
  }
162321
162362
  case "iv":
162322
162363
  if (isPartDecl(node)) {
@@ -162387,6 +162428,8 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162387
162428
  // they get their graphical home as General View usage nodes — previously a
162388
162429
  // package-level `constraint massLimitation { … }` was invisible everywhere.
162389
162430
  isViewlessUsage(n2) {
162431
+ if (n2.$type === "FeatureShorthand" && isOccurrenceModified(n2) && nameOf2(n2) !== void 0)
162432
+ return true;
162390
162433
  return (isConstraintDecl(n2) || isCalcDecl(n2) || isOccurrenceDecl(n2) || isItemDecl(n2) || isAttributeDecl(n2)) && n2.isDef !== true && nameOf2(n2) !== void 0;
162391
162434
  }
162392
162435
  isDirectPackageMember(n2) {
@@ -162411,9 +162454,9 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162411
162454
  const { uri, index: index2 } = ctx;
162412
162455
  const scope = this.packageScopeOf(ctx.anchor);
162413
162456
  const all = [...ast_utils_exports.streamAllContents(scope)];
162414
- const defNodes = all.filter((n2) => isDefinitionKind(n2) && n2.isDef === true && nameOf2(n2));
162457
+ const defNodes = all.filter((n2) => isDefinitionNode(n2) && nameOf2(n2));
162415
162458
  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));
162459
+ 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
162460
  const packageNodes = [
162418
162461
  ...packageOverview && hasOwnedMemberNode && isPackage(scope) && nameOf2(scope) ? [scope] : [],
162419
162462
  ...all.filter((p) => isPackage(p) && nameOf2(p))
@@ -162603,16 +162646,18 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162603
162646
  const boundReferences = [];
162604
162647
  for (let w = 0; w < featureWork.length; w += 1) {
162605
162648
  const d = featureWork[w];
162606
- for (const m of membersOf(d)) {
162649
+ for (const raw of membersOf(d)) {
162650
+ const m = this.thenActionMemberNode(raw) ?? raw;
162607
162651
  const mm = m;
162608
- if (mm.isDef === true || !nameOf2(m))
162652
+ const label = portionLabelOf(m);
162653
+ if (mm.isDef === true || !label)
162609
162654
  continue;
162610
162655
  if (directionOf(m) !== void 0)
162611
162656
  continue;
162612
- if (!isFeatureUsageMember(m, d))
162657
+ if (!isFeatureUsageMember(m, d) && !isOccurrenceModified(m))
162613
162658
  continue;
162614
162659
  const isRefMembership = (mm.modifiers ?? []).includes("ref");
162615
- if (!isPartDecl(m) && !isOccurrenceDecl(m))
162660
+ if (!isPartDecl(m) && !isOccurrenceDecl(m) && !isOccurrenceModified(m) && !isDeclaredEventOccurrence(m))
162616
162661
  continue;
162617
162662
  const boundTarget = isRefMembership ? boundPartTarget(m) : void 0;
162618
162663
  const hasBoundPath = isRefMembership && isPathExpr(mm.value);
@@ -162625,14 +162670,16 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162625
162670
  }
162626
162671
  const def = this.resolveType(m, index2);
162627
162672
  const defOnCanvas = !!(def && ids.has(def));
162628
- const uid = qnameOf(m) || nameOf2(m);
162673
+ const uid = diagramNodeId(m) ?? label;
162629
162674
  if (!ids.has(m)) {
162630
162675
  ids.set(m, uid);
162631
- if (!byName.has(nameOf2(m)))
162632
- byName.set(nameOf2(m), m);
162676
+ if (!byName.has(label))
162677
+ byName.set(label, m);
162678
+ if (!byQName.has(uid))
162679
+ byQName.set(uid, m);
162633
162680
  nodes.push({
162634
162681
  id: uid,
162635
- name: nameOf2(m),
162682
+ name: label,
162636
162683
  keyword: keywordFor(m),
162637
162684
  isDef: false,
162638
162685
  shape: "box",
@@ -162657,10 +162704,16 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162657
162704
  id: `e${e++}`,
162658
162705
  from: ids.get(d),
162659
162706
  to: uid,
162660
- kind: isRefMembership ? "reference" : "composition",
162707
+ // REQ-160 a composed `timeslice`/`snapshot` is not ordinary
162708
+ // feature membership: it is the temporal decomposition of the
162709
+ // owner's life (OMG 8.2.3.9), drawn with the hollow triangle at
162710
+ // the whole end. A `ref` portion still reads as a reference — it
162711
+ // points at a portion owned elsewhere rather than carving up this
162712
+ // occurrence's own lifetime.
162713
+ kind: isRefMembership ? "reference" : portionKindOf(m) ? "portion" : "composition",
162661
162714
  // No label — the usage name/type read inside the box; keep
162662
162715
  // `name` for the stable route key and Properties.
162663
- name: nameOf2(m),
162716
+ name: label,
162664
162717
  type: typeText(m),
162665
162718
  source: sourceOf2(m, uri)
162666
162719
  });
@@ -162669,6 +162722,31 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
162669
162722
  }
162670
162723
  }
162671
162724
  }
162725
+ for (const owner of [...ids.keys()]) {
162726
+ const from = ids.get(owner);
162727
+ for (const raw of membersOf(owner)) {
162728
+ const m = this.thenActionMemberNode(raw) ?? raw;
162729
+ if (!isEventDecl(m) || m.isOccurrence === true)
162730
+ continue;
162731
+ const ev = m;
162732
+ const path10 = [nameOf2(m), ...ev.chainSegs].filter(Boolean).join("::");
162733
+ if (!path10)
162734
+ continue;
162735
+ const target = resolveDrawnByText(path10);
162736
+ const to = target && ids.get(target);
162737
+ if (!to || to === from)
162738
+ continue;
162739
+ edges.push({
162740
+ id: `e${e++}`,
162741
+ from,
162742
+ to,
162743
+ kind: "referenceSubsetting",
162744
+ label: "event",
162745
+ name: lastSeg(path10),
162746
+ source: sourceOf2(m, uri)
162747
+ });
162748
+ }
162749
+ }
162672
162750
  for (const binding of boundReferences) {
162673
162751
  const from = ids.get(binding.owner);
162674
162752
  const to = ids.get(binding.target);
@@ -165863,7 +165941,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
165863
165941
  const related = this.relatedNodes(anchor, index2);
165864
165942
  const keepIds = /* @__PURE__ */ new Set();
165865
165943
  for (const n2 of related) {
165866
- const id2 = qnameOf(n2) || nameOf2(n2);
165944
+ const id2 = diagramNodeId(n2);
165867
165945
  if (id2)
165868
165946
  keepIds.add(id2);
165869
165947
  }
@@ -166073,7 +166151,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166073
166151
  // One registry: each compartment is defined once with the member predicate
166074
166152
  // that fills it, emitted in a single canonical order that satisfies the
166075
166153
  // requirement order (doc → subject → attributes → assume → require →
166076
- // actors → stakeholders → frame concerns, REQ-183) and the use-case order
166154
+ // actors → stakeholders → frames, REQ-183) and the use-case order
166077
166155
  // (subject → actors → include use cases, REQ-184). Empty compartments are
166078
166156
  // omitted (REQ-181) — except `subject`, which is mandatory on requirement /
166079
166157
  // use-case / verification-case definitions (REQ-183/184) and shows a
@@ -166087,7 +166165,10 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166087
166165
  const t = typeText(m);
166088
166166
  return `${dir ? `${dir} ` : ""}${derived}${nameOf2(m) ?? ""}${t ? ` : ${conj}${t}` : ""}`.trim();
166089
166167
  }
166090
- /** REQ-177/REQ-187 — inherited features (`^name`), dropped when redefined locally. */
166168
+ /** REQ-177/REQ-187 — inherited attributes, dropped when redefined locally.
166169
+ * Their textual home is selected later by the same classifier as local
166170
+ * members; retaining the AST member here prevents direction/variability
166171
+ * from being flattened into `attributes` before classification. */
166091
166172
  inheritedItems(node, index2, uri) {
166092
166173
  if (node.isDef === true)
166093
166174
  return [];
@@ -166104,22 +166185,45 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166104
166185
  }
166105
166186
  }
166106
166187
  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) }));
166188
+ 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
166189
  }
166109
166190
  compartmentsFor(node, uri, index2, featureRows = false, includeInherited = true) {
166110
166191
  const members = membersOf(node);
166111
166192
  const n2 = node;
166112
166193
  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));
166194
+ const isVariantConfigMember = (m) => m.allVariants === true;
166195
+ const isVariantMember = (m) => modifiersOf(m).includes("variant") && !isVariantConfigMember(m);
166196
+ const isNonVariantMember = (m) => !isVariantMember(m) && !isVariantConfigMember(m);
166197
+ const isOrdinaryMember = (m) => directionOf(m) === void 0 && isNonVariantMember(m);
166198
+ const inherited = includeInherited ? this.inheritedItems(node, index2, uri) : [];
166199
+ const inheritedRows = (predicate, text = (member) => this.featureText(member)) => inherited.filter(({ member }) => predicate(member)).map(({ member, source }) => ({ text: `^${text(member)}`, source }));
166200
+ const usages = (guard) => members.filter((m) => guard(m) && m.isDef !== true && nameOf2(m) && isOrdinaryMember(m));
166114
166201
  const exprText3 = (m) => (m.body?.$cstNode?.text ?? "").replace(/\s+/g, " ").trim();
166115
166202
  const statementText = (m) => (m.$cstNode?.text ?? this.featureText(m)).replace(/\s+/g, " ").replace(/;$/, "").trim();
166203
+ const canonicalFeatureText = (m) => m.$type === "FeatureShorthand" || m.$type === "FeatureRedefinitionShorthand" ? statementText(m) : this.featureText(m);
166116
166204
  const out = [];
166205
+ const claimedSources = /* @__PURE__ */ new Set();
166206
+ const sourceKey = (source) => `${source.uri}:${source.range.start.line}:${source.range.start.character}:${source.range.end.line}:${source.range.end.character}`;
166117
166207
  const add = (title, items) => {
166118
- if (items.length)
166119
- out.push({ title, items });
166208
+ const accepted = items.filter((candidate) => {
166209
+ if (!candidate.source)
166210
+ return true;
166211
+ const key = sourceKey(candidate.source);
166212
+ if (claimedSources.has(key))
166213
+ return false;
166214
+ claimedSources.add(key);
166215
+ return true;
166216
+ });
166217
+ if (!accepted.length)
166218
+ return;
166219
+ const existing = out.find((compartment) => compartment.title === title);
166220
+ if (existing)
166221
+ existing.items.push(...accepted);
166222
+ else
166223
+ out.push({ title, items: accepted });
166120
166224
  };
166121
166225
  add("doc", members.filter((m) => m.$type === "DocCommentMember").map((m) => item(m, notePreview(docCommentBody(m)) ?? "doc")));
166122
- const subjects = members.filter(isSubjectDecl);
166226
+ const subjects = members.filter(isSubjectDecl).filter(isNonVariantMember);
166123
166227
  const subjectMandatory = n2.isDef === true && (isRequirementDecl(node) || isUseCaseDecl(node) || isVerificationCaseDecl(node));
166124
166228
  if (subjects.length) {
166125
166229
  add("subject", subjects.map((s) => item(s)));
@@ -166128,34 +166232,53 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166128
166232
  }
166129
166233
  add("attributes", [
166130
166234
  ...usages(isAttributeDecl).map((a2) => item(a2)),
166131
- ...includeInherited ? this.inheritedItems(node, index2, uri) : []
166235
+ ...inheritedRows((member) => isAttributeDecl(member) && isOrdinaryMember(member))
166132
166236
  ]);
166237
+ const notPortion = (m) => portionKindOf(m) === void 0;
166133
166238
  if (featureRows) {
166134
166239
  add("ports", usages(isPortDecl).map((m) => item(m)));
166135
- add("parts", usages(isPartDecl).map((m) => item(m)));
166240
+ add("parts", usages(isPartDecl).filter(notPortion).map((m) => item(m)));
166136
166241
  }
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)));
166242
+ add("features", members.filter((m) => m.$type === "FeatureShorthand" || m.$type === "FeatureRedefinitionShorthand").filter(isOrdinaryMember).filter((m) => !isOccurrenceModified(m)).map((m) => item(m, canonicalFeatureText(m))));
166243
+ const undirected = (guard) => usages(guard);
166244
+ add("items", undirected(isItemDecl).filter(notPortion).map((m) => item(m)));
166245
+ add("occurrences", undirected(isOccurrenceDecl).filter(notPortion).map((m) => item(m)));
166246
+ 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
166247
  add("calcs", undirected(isCalcDecl).map((m) => item(m)));
166143
166248
  add("constraints", undirected(isConstraintDecl).map((m) => item(m)));
166144
166249
  add("assume constraints", members.filter((m) => m.$type === "AssumeConstraintStmt").map((m) => item(m, exprText3(m) || "assume constraint")));
166145
166250
  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")));
166251
+ add("actors", members.filter(isActorDecl).filter(isNonVariantMember).map((m) => item(m)));
166252
+ add("stakeholders", members.filter(isStakeholderDecl).filter(isNonVariantMember).map((m) => item(m)));
166253
+ add("frames", members.filter((m) => m.$type === "FrameMember").map((m) => item(m, this.refText(m.target) ?? "concern")));
166149
166254
  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")));
166255
+ add("objective", members.filter(isObjectiveDecl).filter(isNonVariantMember).map((m) => item(m, nameOf2(m) ?? "objective")));
166151
166256
  add("verifies", members.filter((m) => m.$type === "VerifyStmt").map((m) => item(m, String(m.target ?? ""))));
166152
166257
  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)}` : ""}`)));
166258
+ const behavioralParameterOwner = isActionDecl(node) || isCalcDecl(node) || isConstraintDecl(node) || isCaseDecl(node) || isAnalysisCaseDecl(node) || isVerificationCaseDecl(node) || isUseCaseDecl(node);
166259
+ const enclosingBehavioralParameterOwner = (() => {
166260
+ if (isPartDecl(node) || isPortDecl(node) || directionOf(node) === void 0)
166261
+ return false;
166262
+ let owner = node.$container;
166263
+ while (owner) {
166264
+ if (isActionDecl(owner) || isCalcDecl(owner) || isConstraintDecl(owner) || isCaseDecl(owner) || isAnalysisCaseDecl(owner) || isVerificationCaseDecl(owner) || isUseCaseDecl(owner))
166265
+ return true;
166266
+ if (isPartDecl(owner) || isPortDecl(owner))
166267
+ return false;
166268
+ owner = owner.$container;
166269
+ }
166270
+ return false;
166271
+ })();
166272
+ const directedTitle = behavioralParameterOwner || enclosingBehavioralParameterOwner ? "parameters" : "directed features";
166273
+ add(directedTitle, [
166274
+ ...members.filter((m) => directionOf(m) !== void 0 && nameOf2(m)).filter((m) => !isVariantMember(m) && !isVariantConfigMember(m)).map((m) => item(m, canonicalFeatureText(m))),
166275
+ ...inheritedRows((member) => directionOf(member) !== void 0 && isNonVariantMember(member))
166276
+ ]);
166277
+ add("result", members.filter((m) => m.$type === "ReturnDecl").map((m) => item(m, `return${nameOf2(m) ? ` ${nameOf2(m)}` : ""}${typeText(m) ? ` : ${typeText(m)}` : ""}`)));
166155
166278
  if (isConstraintDecl(node))
166156
166279
  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) => {
166280
+ const enumValueMembers = members.filter(isNonVariantMember).filter((m) => m.$type === "EnumValueDecl" && (isEnumDecl(node) || !m.typing) || isEnumDecl(node) && isEnumDecl(m) && m.isDef !== true);
166281
+ add("enums", enumValueMembers.map((m) => {
166159
166282
  const nm = nameOf2(m);
166160
166283
  if (nm)
166161
166284
  return item(m, nm);
@@ -166165,7 +166288,7 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166165
166288
  add("states", usages(isStateDecl).map((m) => item(m)));
166166
166289
  const stateActions = members.map((m) => ({ m, kind: { EntryAction: "entry", DoAction: "do", ExitAction: "exit" }[m.$type] })).filter((x) => !!x.kind);
166167
166290
  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 ?? ""}`)));
166291
+ add("state transition", members.filter((m) => m.$type === "TransitionDecl").map((m) => item(m, `${m.from ?? ""} \u2192 ${m.to ?? ""}`)));
166169
166292
  add("assert constraints", members.filter((m) => m.$type === "AssertConstraintStmt").map((m) => item(m, exprText3(m) || nameOf2(m) || "assert constraint")));
166170
166293
  add("invariants", members.filter((m) => m.$type === "InvShorthand").map((m) => {
166171
166294
  const inv = m;
@@ -166173,15 +166296,22 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166173
166296
  const expr = exprText3(m);
166174
166297
  return item(m, `${head2} { ${expr} }`.replace(/\s+/g, " ").trim());
166175
166298
  }));
166176
- add("action flow", usages(isActionDecl).map((m) => item(m)));
166299
+ add("actions", usages(isActionDecl).map((m) => item(m)));
166177
166300
  add("concerns", usages(isConcernDecl).map((m) => item(m)));
166178
166301
  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) ?? ""))));
166302
+ 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
166303
  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
166304
  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)));
166305
+ const portionMembers = members.map((m) => this.thenActionMemberNode(m) ?? m).filter(isNonVariantMember);
166306
+ const withPortionKind = (kind) => portionMembers.filter((m) => portionKindOf(m) === kind && portionLabelOf(m));
166307
+ const portionText = (m) => {
166308
+ const t = typeText(m);
166309
+ const mult = multText(m);
166310
+ return `${portionLabelOf(m)}${t ? ` : ${t}` : ""}${mult ? ` ${mult}` : ""}`;
166311
+ };
166312
+ add("timeslices", withPortionKind("timeslice").map((m) => item(m, portionText(m))));
166313
+ add("snapshots", withPortionKind("snapshot").map((m) => item(m, portionText(m))));
166314
+ add("individuals", portionMembers.filter((m) => m.$type === "FeatureShorthand" && isIndividualOccurrence(m) && portionKindOf(m) === void 0 && portionLabelOf(m)).map((m) => item(m, portionText(m))));
166185
166315
  const metaBadges = [
166186
166316
  ...prefixTagsOf(node).map((t) => `#${t}`),
166187
166317
  ...members.filter((m) => m.$type === "MetadataAnnotation").map((m) => {
@@ -166192,8 +166322,15 @@ var SysmlDiagramModelProvider = class _SysmlDiagramModelProvider {
166192
166322
  })
166193
166323
  ];
166194
166324
  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())));
166325
+ add("variants", [
166326
+ ...members.filter((m) => isVariantMember(m) && nameOf2(m)).map((m) => item(m)),
166327
+ ...inheritedRows((member) => isVariantMember(member) && !!nameOf2(member))
166328
+ ]);
166329
+ const variantElementText = (member) => `${nameOf2(member) ?? ""} = all ${(member.value?.$cstNode?.text ?? "").trim()}`.trim();
166330
+ add("variant element usages", [
166331
+ ...members.filter(isVariantConfigMember).map((m) => item(m, variantElementText(m))),
166332
+ ...inheritedRows(isVariantConfigMember, variantElementText)
166333
+ ]);
166197
166334
  add("satisfies", members.filter((m) => m.$type === "SatisfyStmt").map((m) => item(m, String(m.target ?? ""))));
166198
166335
  return out.length ? out : void 0;
166199
166336
  }
@@ -166811,7 +166948,7 @@ function symbolNameFor(node) {
166811
166948
  const redefined = redefinitionTargets(node)[0];
166812
166949
  return redefined ? `:>> ${lastNameSegment(redefined)}` : void 0;
166813
166950
  }
166814
- function isDefinitionNode(node) {
166951
+ function isDefinitionNode2(node) {
166815
166952
  return node.isDef === true;
166816
166953
  }
166817
166954
  function isInheritableFeature(node) {
@@ -166951,7 +167088,7 @@ function findNameLeaf(node, name) {
166951
167088
  return void 0;
166952
167089
  }
166953
167090
  function inheritedFeatureSymbols(node, inheritedRange, context, seen = /* @__PURE__ */ new Set()) {
166954
- if (!isDefinitionNode(node) || seen.has(node))
167091
+ if (!isDefinitionNode2(node) || seen.has(node))
166955
167092
  return [];
166956
167093
  seen.add(node);
166957
167094
  const symbols = [];
@@ -167876,6 +168013,13 @@ var DIAGNOSTIC_MESSAGES = {
167876
168013
  // implicit-redefinition resolution this validator does not yet model.
167877
168014
  RES017_DUPLICATE_MEMBER: (name) => `'${name}' is already declared in this namespace. Members must be distinguishable by name \u2014 rename or remove the duplicate.`,
167878
168015
  RES017_DUPLICATE_SHORT_NAME: (name) => `Short name '<${name}>' is already used in this namespace. Members must be distinguishable \u2014 rename the short name.`,
168016
+ // issue #102 — REQ-387. The filter evaluator (`import-visibility.ts`,
168017
+ // `evaluateFilterCondition`) decides metadata-presence tests and the Boolean
168018
+ // operators over them; every other form fails OPEN, keeping the member. Failing
168019
+ // open is the right default — under-importing would manufacture spurious
168020
+ // RES001s — but an unevaluated filter produces a scope or view that looks
168021
+ // authoritative and is not, so the fail-open is stated rather than assumed.
168022
+ RES018_UNEVALUATED_FILTER: (subject, construct) => `${subject} filter is not evaluated: ${construct} is outside the metadata-filter evaluator, which decides '@'/'@@' metadata tests combined with 'not', 'and'/'&', and 'or'/'|'. The filter fails open \u2014 it is treated as pass-through and removes nothing.`,
167879
168023
  // REQ-007 — SysML v1 → v2 migration guardrail.
167880
168024
  MIG001_V1_KEYWORD: (keyword, v2, note) => `'${keyword}' is a SysML v1 construct, not a SysML v2 keyword. ${note} Use '${v2}'.`,
167881
168025
  MIG002_V1_STEREOTYPE_KNOWN: (stereotype, v2, note) => `SysML v2 has no stereotypes. ${note} Replace \xAB${stereotype}\xBB with '${v2}'.`,
@@ -170333,14 +170477,14 @@ function evaluateFilterCondition(node, metadataTypes, resolveRefs) {
170333
170477
  return node.items.length === 1 ? evaluateFilterCondition(node.items[0], metadataTypes, resolveRefs) : void 0;
170334
170478
  }
170335
170479
  if (isBinExpr(node)) {
170336
- if (node.op === "and" || node.op === "&") {
170480
+ if (CONJUNCTION_OPERATORS.has(node.op)) {
170337
170481
  const left = evaluateFilterCondition(node.left, metadataTypes, resolveRefs);
170338
170482
  const right = evaluateFilterCondition(node.right, metadataTypes, resolveRefs);
170339
170483
  if (left === false || right === false)
170340
170484
  return false;
170341
170485
  return left === void 0 || right === void 0 ? void 0 : true;
170342
170486
  }
170343
- if (node.op === "or" || node.op === "|") {
170487
+ if (DISJUNCTION_OPERATORS.has(node.op)) {
170344
170488
  const left = evaluateFilterCondition(node.left, metadataTypes, resolveRefs);
170345
170489
  const right = evaluateFilterCondition(node.right, metadataTypes, resolveRefs);
170346
170490
  if (left === true || right === true)
@@ -170351,6 +170495,42 @@ function evaluateFilterCondition(node, metadataTypes, resolveRefs) {
170351
170495
  }
170352
170496
  return void 0;
170353
170497
  }
170498
+ var CONJUNCTION_OPERATORS = /* @__PURE__ */ new Set(["and", "&"]);
170499
+ var DISJUNCTION_OPERATORS = /* @__PURE__ */ new Set(["or", "|"]);
170500
+ function undecidableFilterCondition(condition) {
170501
+ if (!condition)
170502
+ return void 0;
170503
+ if (isSelfClassifyExpr(condition)) {
170504
+ return condition.op === "@" || condition.op === "@@" ? void 0 : { node: condition, construct: `the '${condition.op}' classification operator` };
170505
+ }
170506
+ if (isNotExpr(condition) && condition.operand) {
170507
+ return undecidableFilterCondition(condition.operand);
170508
+ }
170509
+ if (condition.$type === "NotExpr")
170510
+ return void 0;
170511
+ if (isParenExpr(condition)) {
170512
+ return condition.items.length === 1 ? undecidableFilterCondition(condition.items[0]) : { node: condition, construct: "a parenthesized expression list" };
170513
+ }
170514
+ if (isBinExpr(condition)) {
170515
+ if (CONJUNCTION_OPERATORS.has(condition.op) || DISJUNCTION_OPERATORS.has(condition.op)) {
170516
+ return undecidableFilterCondition(condition.left) ?? undecidableFilterCondition(condition.right);
170517
+ }
170518
+ return { node: condition, construct: `the '${condition.op}' operator` };
170519
+ }
170520
+ return { node: condition, construct: describeFilterExpr(condition) };
170521
+ }
170522
+ function describeFilterExpr(node) {
170523
+ if (isPostfixOp(node)) {
170524
+ if ((node.dot || node.select) && node.field)
170525
+ return `the '.${node.field}' feature access`;
170526
+ if (node.arrow && node.invoke)
170527
+ return `the '->${node.invoke}' invocation`;
170528
+ return "this postfix expression";
170529
+ }
170530
+ if (isPathExpr(node))
170531
+ return "a plain feature reference";
170532
+ return "this expression form";
170533
+ }
170354
170534
  function attachedMetadataTypes(node, resolveRefs) {
170355
170535
  const siblings = node.$container?.members;
170356
170536
  if (!Array.isArray(siblings))
@@ -170579,6 +170759,26 @@ function actionOnlyConstruct(node) {
170579
170759
  const keyword = CONTROL_NODE_KEYWORDS[node.$type];
170580
170760
  return keyword ? { construct: `A '${keyword}' control node`, clause: "7.17.3" } : void 0;
170581
170761
  }
170762
+ function portionKindOf2(node) {
170763
+ const mods = node.modifiers;
170764
+ if (!Array.isArray(mods))
170765
+ return void 0;
170766
+ return mods.find((m) => m === "timeslice" || m === "snapshot");
170767
+ }
170768
+ var NAMESPACE_ONLY_TYPES = /* @__PURE__ */ new Set(["Package", "Document", "NamespaceDecl"]);
170769
+ function owningTypeOf(node) {
170770
+ let owner = node.$container;
170771
+ while (owner && (owner.$type === "PrefixMetadataMember" || owner.$type === "ThenActionMember")) {
170772
+ owner = owner.$container;
170773
+ }
170774
+ if (!owner)
170775
+ return void 0;
170776
+ return NAMESPACE_ONLY_TYPES.has(owner.$type) ? void 0 : owner;
170777
+ }
170778
+ function declLabel(node, kind) {
170779
+ const name = node.name;
170780
+ return name ? `'${name}'` : `this '${kind}'`;
170781
+ }
170582
170782
  var SysmlValidator = class _SysmlValidator {
170583
170783
  indexManager;
170584
170784
  langiumDocuments;
@@ -170802,6 +171002,7 @@ var SysmlValidator = class _SysmlValidator {
170802
171002
  this.checkLanguageDisjointness(node, accept);
170803
171003
  this.checkKermlWellFormedness(node, accept);
170804
171004
  this.checkFilterExpressions(node, accept);
171005
+ this.checkOccurrencePortions(node, accept);
170805
171006
  this.checkCrossReferences(node, accept);
170806
171007
  this.checkAutoImportSuggestions(node, accept);
170807
171008
  this.checkUnits(node, accept);
@@ -171629,6 +171830,7 @@ ${baseIndent}}`;
171629
171830
  checkCrossReferences(node, accept) {
171630
171831
  const index2 = this.buildIndex();
171631
171832
  const imports = [];
171833
+ const exposePaths = [];
171632
171834
  const decls = [];
171633
171835
  const verifyStmts = [];
171634
171836
  const satisfyStmts = [];
@@ -171640,7 +171842,9 @@ ${baseIndent}}`;
171640
171842
  const path10 = importedPath(child);
171641
171843
  if (alias && path10 && importWildcard(child) === "none")
171642
171844
  aliasMap.set(alias, path10);
171643
- } else if (isVerifyStmt(child))
171845
+ } else if (isExposePath(child))
171846
+ exposePaths.push(child);
171847
+ else if (isVerifyStmt(child))
171644
171848
  verifyStmts.push(child);
171645
171849
  else if (isSatisfyStmt(child))
171646
171850
  satisfyStmts.push(child);
@@ -171656,6 +171860,7 @@ ${baseIndent}}`;
171656
171860
  this.checkPrivateImports(imports, index2, accept);
171657
171861
  this.checkPrivateWildcardReferences(node, imports, index2, accept);
171658
171862
  this.checkImportCycles(imports, index2, accept);
171863
+ this.checkUnevaluatedFilters(imports, exposePaths, accept);
171659
171864
  if (sysmlDiagnosticSettings.unusedImports !== "off") {
171660
171865
  this.checkUnusedImports(node, imports, index2, sysmlDiagnosticSettings.unusedImports, accept);
171661
171866
  }
@@ -171685,6 +171890,49 @@ ${baseIndent}}`;
171685
171890
  this.checkTypeComposition(decl, index2, accept);
171686
171891
  }
171687
171892
  }
171893
+ // ══════════════════════════════════════════════════════════════════════
171894
+ // REQ-160 — occurrence portions: `timeslice` / `snapshot` well-formedness
171895
+ // ══════════════════════════════════════════════════════════════════════
171896
+ // The declaration kinds that are NOT occurrences, so a portion of their life is
171897
+ // meaningless. Kept as a deny-list rather than an allow-list because nearly every
171898
+ // SysML usage kind IS an OccurrenceUsage (part, item, action, state, port,
171899
+ // connection, case, …) while the data-valued ones are few and closed: an
171900
+ // AttributeUsage is typed by a DataType and an enumeration is a special attribute,
171901
+ // neither of which has a life to carve up.
171902
+ static NON_OCCURRENCE_OWNER_TYPES = /* @__PURE__ */ new Set(["AttributeDecl", "EnumDecl", "EnumValueDecl", "MetadataDecl", "MultiplicityDecl"]);
171903
+ // REQ-160 — OMG SysML v2 §8.2.2.9 (Occurrences) with the Kernel Semantic Library
171904
+ // `Occurrences::TimeSlice` / `Occurrences::Snapshot`:
171905
+ //
171906
+ // SSM015 — a portion is a portion OF something. Its owning type must be an
171907
+ // occurrence; declared at package level (or inside an attribute /
171908
+ // enumeration body) there is no life for it to be a portion of.
171909
+ // SSM016 — `Snapshot :> TimeSlice` with a zero duration: a snapshot is the
171910
+ // occurrence at ONE instant. Every portion of a zero-duration
171911
+ // occurrence is itself zero-duration, so a snapshot may own further
171912
+ // snapshots but never a `timeslice`.
171913
+ //
171914
+ // Both are structural — they read modifiers and owners only — so they hold
171915
+ // without resolving a single cross-reference.
171916
+ checkOccurrencePortions(node, accept) {
171917
+ for (const child of ast_utils_exports.streamAllContents(node)) {
171918
+ const kind = portionKindOf2(child);
171919
+ if (!kind)
171920
+ continue;
171921
+ const label = declLabel(child, kind);
171922
+ const owner = owningTypeOf(child);
171923
+ if (!owner) {
171924
+ 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" });
171925
+ continue;
171926
+ }
171927
+ if (_SysmlValidator.NON_OCCURRENCE_OWNER_TYPES.has(owner.$type)) {
171928
+ 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" });
171929
+ continue;
171930
+ }
171931
+ if (kind === "timeslice" && portionKindOf2(owner) === "snapshot") {
171932
+ 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" });
171933
+ }
171934
+ }
171935
+ }
171688
171936
  // REQ-328 — KSM008: a `filter` membership condition must be Boolean. Only a
171689
171937
  // *definitely* non-Boolean expression is flagged — a string or numeric/quantity
171690
171938
  // literal, or an arithmetic/range binary expression. A bare feature path is left
@@ -172231,6 +172479,34 @@ ${baseIndent}}`;
172231
172479
  }
172232
172480
  return false;
172233
172481
  }
172482
+ /**
172483
+ * REQ-387 — RES018: an `import` or `expose` filter the metadata-filter
172484
+ * evaluator cannot decide (issue #102).
172485
+ *
172486
+ * The evaluator keeps a member it cannot decide, so an unevaluated filter
172487
+ * silently contributes the members it was written to exclude — a scope or a
172488
+ * view that reads as authoritative and is not. Failing open stays (narrowing
172489
+ * on a guess would manufacture unresolved-name errors); what changes is that
172490
+ * the model now says so at the filter that is not applied.
172491
+ *
172492
+ * Default severity is `info` rather than a warning: the model is legal and the
172493
+ * limitation is this evaluator's, not the author's. Teams that want an
172494
+ * unevaluated filter to break a build raise it per code through
172495
+ * `sysml.validation.severities` (REQ-240).
172496
+ */
172497
+ checkUnevaluatedFilters(imports, exposePaths, accept) {
172498
+ const report2 = (condition, subject) => {
172499
+ const undecidable = undecidableFilterCondition(condition);
172500
+ if (!undecidable)
172501
+ return;
172502
+ accept(severity("RES018", "info"), DIAGNOSTIC_MESSAGES.RES018_UNEVALUATED_FILTER(subject, undecidable.construct), { node: undecidable.node, code: "RES018" });
172503
+ };
172504
+ for (const imp of imports)
172505
+ for (const filter4 of imp.filters)
172506
+ report2(filter4.condition, "Import");
172507
+ for (const path10 of exposePaths)
172508
+ report2(path10.condition, "Expose");
172509
+ }
172234
172510
  // REQ-324 — RES009 unused import (configurable, OFF by default)
172235
172511
  checkUnusedImports(root4, imports, index2, level, accept) {
172236
172512
  if (imports.length === 0)
@@ -176534,6 +176810,7 @@ function readProjectConfig(root4) {
176534
176810
  // ../extension/src/webview/diagram/flow/diagram-rules.ts
176535
176811
  var PORT_GLYPH_SIZE = 20;
176536
176812
  var PORT_GLYPH_HALF = PORT_GLYPH_SIZE / 2;
176813
+ var FEATURE_COMPARTMENT_CLEARANCE = PORT_GLYPH_HALF + 12;
176537
176814
  var ACTION_PIN_GLYPH_SIZE = PORT_GLYPH_SIZE;
176538
176815
  function endpointGlyphSize(endpoint) {
176539
176816
  return endpoint.pin ? ACTION_PIN_GLYPH_SIZE : PORT_GLYPH_SIZE;
@@ -176576,18 +176853,26 @@ var DOCK_TANGENT = {
176576
176853
  left: { x: 0, y: 1 },
176577
176854
  right: { x: 0, y: 1 }
176578
176855
  };
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;
176856
+ function portGlyphRect(g, w, h, topReserve = 0) {
176586
176857
  const natural = endpointGlyphSize(g.port);
176587
176858
  const along = g.along ?? natural;
176588
176859
  const toggleRun = g.toggleRun ?? 0;
176589
176860
  const across = g.across ?? (along - toggleRun > natural ? NESTED_PORT_HOST_ACROSS : natural);
176590
176861
  const vertical = g.side === "left" || g.side === "right";
176862
+ const base = sidePoint(g.side, g.offset, w, h, topReserve);
176863
+ const t = DOCK_TANGENT[g.side];
176864
+ const n2 = DOCK_NORMAL[g.side];
176865
+ const shift = g.shift ?? { along: 0, across: 0 };
176866
+ const rawCx = base.x + t.x * shift.along + n2.x * shift.across;
176867
+ const rawCy = base.y + t.y * shift.along + n2.y * shift.across;
176868
+ const run = sideLength(g.side, w, h, topReserve);
176869
+ const alongCentre = vertical ? rawCy : rawCx;
176870
+ const rootAlong = g.rootAlong ?? along;
176871
+ const rootCentre = vertical ? base.y : base.x;
176872
+ const boundedRootCentre = topReserve > 0 && vertical ? run >= rootAlong ? Math.max(topReserve + rootAlong / 2, Math.min(topReserve + run - rootAlong / 2, rootCentre)) : topReserve + run / 2 : rootCentre;
176873
+ const boundedAlong = alongCentre + boundedRootCentre - rootCentre;
176874
+ const cx = vertical ? rawCx : boundedAlong;
176875
+ const cy = vertical ? boundedAlong : rawCy;
176591
176876
  const width = vertical ? across : along;
176592
176877
  const height = vertical ? along : across;
176593
176878
  return {
@@ -176661,18 +176946,40 @@ var DOCK_NORMAL = {
176661
176946
  bottom: { x: 0, y: 1 },
176662
176947
  left: { x: -1, y: 0 }
176663
176948
  };
176664
- function sidePoint(side, offset2, w, h) {
176949
+ function portSideHeight(h, topReserve = 0) {
176950
+ const band = Number.isFinite(topReserve) ? Math.max(0, Math.min(Math.max(0, h), topReserve)) : 0;
176951
+ return Math.max(0, h - band);
176952
+ }
176953
+ function sidePoint(side, offset2, w, h, topReserve = 0) {
176954
+ const top = h - portSideHeight(h, topReserve);
176955
+ const sideH = portSideHeight(h, topReserve);
176665
176956
  switch (side) {
176666
176957
  case "top":
176667
176958
  return { x: w * offset2, y: 0 };
176668
176959
  case "bottom":
176669
176960
  return { x: w * offset2, y: h };
176670
176961
  case "left":
176671
- return { x: 0, y: h * offset2 };
176962
+ return { x: 0, y: top + sideH * offset2 };
176672
176963
  case "right":
176673
- return { x: w, y: h * offset2 };
176964
+ return { x: w, y: top + sideH * offset2 };
176674
176965
  }
176675
176966
  }
176967
+ function nearestPortOwnerPlacement(x, y, w, h, topReserve = 0) {
176968
+ const sideH = portSideHeight(h, topReserve);
176969
+ const top = h - sideH;
176970
+ const clamp3 = (value, lo, hi) => Math.max(lo, Math.min(hi, value));
176971
+ const distanceSq = (px, py, qx, qy) => (px - qx) ** 2 + (py - qy) ** 2;
176972
+ const sideY = clamp3(y, top, h);
176973
+ const offsetY = sideH > 0 ? (sideY - top) / sideH : 0.5;
176974
+ const candidates = [
176975
+ { side: "left", offset: offsetY, distance: distanceSq(x, y, 0, sideY) },
176976
+ { side: "right", offset: offsetY, distance: distanceSq(x, y, w, sideY) },
176977
+ { side: "top", offset: w > 0 ? clamp3(x, 0, w) / w : 0.5, distance: distanceSq(x, y, clamp3(x, 0, w), 0) },
176978
+ { side: "bottom", offset: w > 0 ? clamp3(x, 0, w) / w : 0.5, distance: distanceSq(x, y, clamp3(x, 0, w), h) }
176979
+ ];
176980
+ const best = candidates.reduce((nearest, candidate) => candidate.distance < nearest.distance ? candidate : nearest);
176981
+ return { side: best.side, offset: best.offset };
176982
+ }
176676
176983
  var SIDE_ANCHOR_SEP = ":";
176677
176984
  var CONNECT_POINT_SPACING_DEFAULT = 24;
176678
176985
  var CONNECT_POINT_SPACING_MIN = 8;
@@ -176705,12 +177012,12 @@ function centerOutOffsetOrder(offsets) {
176705
177012
  function sidePointOffsetsForLength(length2, spacing) {
176706
177013
  return sidePointOffsets(connectPointCountForLength(length2, spacing));
176707
177014
  }
176708
- function sideLength(side, w, h) {
176709
- return side === "top" || side === "bottom" ? w : h;
177015
+ function sideLength(side, w, h, topReserve = 0) {
177016
+ return side === "top" || side === "bottom" ? w : portSideHeight(h, topReserve);
176710
177017
  }
176711
177018
  var SENDACCEPT_NOTCH = 14;
176712
- function sideEffectiveLength(shape, side, w, h) {
176713
- const raw = sideLength(side, w, h);
177019
+ function sideEffectiveLength(shape, side, w, h, topReserve = 0) {
177020
+ const raw = sideLength(side, w, h, topReserve);
176714
177021
  switch (shape) {
176715
177022
  case "usecase":
176716
177023
  return raw * 0.82;
@@ -176725,7 +177032,7 @@ function sideEffectiveLength(shape, side, w, h) {
176725
177032
  return raw;
176726
177033
  }
176727
177034
  }
176728
- function sideSurfacePoint(shape, side, offset2, w, h) {
177035
+ function sideSurfacePoint(shape, side, offset2, w, h, topReserve = 0) {
176729
177036
  const cx = w / 2;
176730
177037
  const cy = h / 2;
176731
177038
  switch (shape) {
@@ -176753,22 +177060,22 @@ function sideSurfacePoint(shape, side, offset2, w, h) {
176753
177060
  case "send": {
176754
177061
  if (side === "top" || side === "bottom") return { x: (w - SENDACCEPT_NOTCH) * offset2, y: side === "top" ? 0 : h };
176755
177062
  if (side === "right") return { x: w, y: cy };
176756
- return sidePoint(side, offset2, w, h);
177063
+ return sidePoint(side, offset2, w, h, topReserve);
176757
177064
  }
176758
177065
  case "accept": {
176759
177066
  if (side === "left") return { x: SENDACCEPT_NOTCH, y: cy };
176760
- return sidePoint(side, offset2, w, h);
177067
+ return sidePoint(side, offset2, w, h, topReserve);
176761
177068
  }
176762
177069
  default:
176763
- return sidePoint(side, offset2, w, h);
177070
+ return sidePoint(side, offset2, w, h, topReserve);
176764
177071
  }
176765
177072
  }
176766
177073
  function isPackageEndpoint(shape, keyword) {
176767
177074
  if (shape === "package") return true;
176768
177075
  return (keyword ?? "").trim().toLowerCase() === "package";
176769
177076
  }
176770
- function sidePointOffsetsFor(side, w, h, spacing, connectable, shape) {
176771
- return connectable ? sidePointOffsetsForLength(sideEffectiveLength(shape, side, w, h), spacing) : [0.5];
177077
+ function sidePointOffsetsFor(side, w, h, spacing, connectable, shape, topReserve = 0) {
177078
+ return connectable ? sidePointOffsetsForLength(sideEffectiveLength(shape, side, w, h, topReserve), spacing) : [0.5];
176772
177079
  }
176773
177080
  function sideAnchorHandleId(side, offset2) {
176774
177081
  return Math.abs(offset2 - 0.5) < 1e-6 ? side : `${side}${SIDE_ANCHOR_SEP}${offset2.toFixed(3)}`;
@@ -176809,6 +177116,50 @@ function clampDockSide(encoded, portSide) {
176809
177116
  }
176810
177117
  var PORT_PLUS_GAP = 7;
176811
177118
  var PORT_PLUS_HALF = 4;
177119
+ var NODE_USAGE_CORNER_RADIUS = 14;
177120
+ var NODE_ROUNDED_USAGE_RADIUS = 20;
177121
+ var NODE_ROUNDED_TITLED_RADIUS = 12;
177122
+ function nodeCornerRadius(shape, isDef, height, titled) {
177123
+ if (isDef) return 0;
177124
+ if (shape === "action" || shape === "state") {
177125
+ const r = Math.min(height / 2, NODE_ROUNDED_USAGE_RADIUS);
177126
+ return titled ? Math.min(r, NODE_ROUNDED_TITLED_RADIUS) : r;
177127
+ }
177128
+ return NODE_USAGE_CORNER_RADIUS;
177129
+ }
177130
+ var FRAME_USAGE_CORNER_RADIUS = 12;
177131
+ var CONTAINER_CONTROL_SIZE = 20;
177132
+ var CONTAINER_CONTROL_GAP = 1;
177133
+ var CONTAINER_CONTROLS_RIGHT = 2;
177134
+ function containerControlsInset(cornerRadius) {
177135
+ return Math.max(CONTAINER_CONTROLS_RIGHT, Math.ceil(cornerRadius * (1 - Math.SQRT1_2)));
177136
+ }
177137
+ var CONTAINER_CONTROLS_INSET_MAX = containerControlsInset(NODE_ROUNDED_USAGE_RADIUS);
177138
+ var CONTAINER_TITLE_RIGHT_RESERVE = 2 * CONTAINER_CONTROL_SIZE + CONTAINER_CONTROL_GAP + CONTAINER_CONTROLS_INSET_MAX + 11;
177139
+ var COLLAPSED_CONTAINER_MIN_WIDTH = CONTAINER_TITLE_RIGHT_RESERVE + 48;
177140
+ var CONTAINER_GLYPH_VIEWBOX = 20;
177141
+ var CONTAINER_GLYPH_EXTENT = 11;
177142
+ function containerTitleArea(ownerWidth, controlsVisible) {
177143
+ const width = Math.max(0, ownerWidth - (controlsVisible ? CONTAINER_TITLE_RIGHT_RESERVE : 0));
177144
+ return { centerX: width / 2, width };
177145
+ }
177146
+ var NODE_COMPARTMENT_TOP = 30;
177147
+ var FRAME_TITLE_HEIGHT = NODE_COMPARTMENT_TOP;
177148
+ function containerTitleHeight(isFrame) {
177149
+ return isFrame ? FRAME_TITLE_HEIGHT : NODE_COMPARTMENT_TOP;
177150
+ }
177151
+ var CONTAINER_TITLE_KIND_BASELINE = 14;
177152
+ var CONTAINER_TITLE_NAME_BASELINE = 28;
177153
+ function containerTopReserve(bandHeight, isFrame) {
177154
+ return bandHeight && bandHeight > 0 ? containerTitleHeight(isFrame) + bandHeight : 0;
177155
+ }
177156
+ var COMPARTMENT_TITLE_HEIGHT = 16;
177157
+ var COMPARTMENT_ROW_HEIGHT = 14;
177158
+ var COMPARTMENT_TITLE_BASELINE = 12;
177159
+ var COMPARTMENT_ROW_BASELINE = 26;
177160
+ function compartmentBlockHeight(items) {
177161
+ return COMPARTMENT_TITLE_HEIGHT + items * COMPARTMENT_ROW_HEIGHT;
177162
+ }
176812
177163
  function portLabelPlacement(side, x, y, label, half = PORT_GLYPH_HALF, alongHalf = half) {
176813
177164
  const vertical = side === "left" || side === "right";
176814
177165
  const halfX = vertical ? half : alongHalf;
@@ -176846,18 +177197,20 @@ function edgeRouteKey(e, duplicateIndex = 0) {
176846
177197
  const name = e.name ?? e.label;
176847
177198
  return `${e.kind}:${e.from}->${e.to}${name ? `:${name}` : ""}${duplicateIndex > 0 ? `~${duplicateIndex}` : ""}`;
176848
177199
  }
176849
- var GV_TREE_EDGE_KIND_LIST = ["owningMembership", "composition", "reference", "definedBy"];
177200
+ var GV_TREE_EDGE_KIND_LIST = ["owningMembership", "composition", "portion", "reference", "definedBy"];
176850
177201
  var GV_TREE_EDGE_KINDS = new Set(GV_TREE_EDGE_KIND_LIST);
176851
- var GV_TREE_HIERARCHY_EDGE_KIND_LIST = ["owningMembership", "composition"];
177202
+ var GV_TREE_HIERARCHY_EDGE_KIND_LIST = ["owningMembership", "composition", "portion"];
176852
177203
  var GV_TREE_HIERARCHY_EDGE_KINDS = new Set(GV_TREE_HIERARCHY_EDGE_KIND_LIST);
176853
177204
  function gvModeEdges(model, mode) {
176854
177205
  if (mode === "group") return [];
176855
- return model.edges.some((edge) => edge.kind === "owningMembership") ? model.edges.filter((edge) => edge.kind !== "owningMembership") : model.edges;
177206
+ const packageIds = new Set(model.nodes.filter((node) => node.shape === "package").map((node) => node.id));
177207
+ const drawn = (edge) => edge.kind !== "owningMembership" && !packageIds.has(edge.from) && !packageIds.has(edge.to);
177208
+ return model.edges.every(drawn) ? model.edges : model.edges.filter(drawn);
176856
177209
  }
176857
177210
  function isGvUsageNode(n2) {
176858
177211
  return n2.meta?.gvUsage === true;
176859
177212
  }
176860
- var GV_BOXED_USAGE_COMPARTMENTS = /* @__PURE__ */ new Set(["parts", "occurrences"]);
177213
+ var GV_BOXED_USAGE_COMPARTMENTS = /* @__PURE__ */ new Set(["parts", "occurrences", "timeslices", "snapshots", "individuals"]);
176861
177214
  function stripBoxedUsageCompartments(nodes) {
176862
177215
  let changed = false;
176863
177216
  const out = nodes.map((n2) => {
@@ -176898,7 +177251,8 @@ var GV_CATEGORIES = [
176898
177251
  function gvNodeCategory(node) {
176899
177252
  if (node.shape === "package") return "package";
176900
177253
  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();
177254
+ const k = node.keyword.toLowerCase().trim().replace(/^(individual|timeslice|snapshot|parallel|variation|variant|abstract|ref|derived)(\s+|$)/g, "").replace(/\s+def$/, "").trim();
177255
+ if (k === "" || k === "def") return "item";
176902
177256
  const alias = {
176903
177257
  part: "part",
176904
177258
  port: "port",
@@ -176952,9 +177306,19 @@ function twKind(text) {
176952
177306
  function compartmentHeight(node) {
176953
177307
  if (!node.compartments?.length) return 0;
176954
177308
  let h = 0;
176955
- for (const c of node.compartments) h += 16 + c.items.length * 16;
177309
+ for (const c of node.compartments) h += compartmentBlockHeight(c.items.length);
176956
177310
  return h;
176957
177311
  }
177312
+ function featureCompartmentBandHeight(node) {
177313
+ if (node?.meta?.featureCompartmentsVisible !== true || !node.compartments?.length) return 0;
177314
+ return compartmentHeight(node) + FEATURE_COMPARTMENT_CLEARANCE;
177315
+ }
177316
+ function frameFeatureCompartmentHeight(frame2) {
177317
+ return featureCompartmentBandHeight(frame2);
177318
+ }
177319
+ function featureBandTopReserve(data) {
177320
+ return containerTopReserve(data.featureCompartmentHeight, data.frame !== void 0);
177321
+ }
176958
177322
  function titleWidth(node) {
176959
177323
  let t = `${node.name}${node.type ? ` : ${node.type}` : ""}`;
176960
177324
  if (node.multiplicity) t += ` ${node.multiplicity}`;
@@ -177013,10 +177377,11 @@ function nodeSize(node, direction) {
177013
177377
  const sideLabelW = (side) => ph.filter((p) => p.side === side).reduce((m, p) => Math.max(m, tw(p.port.name, 9) + 18), 0);
177014
177378
  const portsW = ph.length ? sideLabelW("left") + sideLabelW("right") + 56 : 0;
177015
177379
  const portRows = Math.max(ph.filter((p) => p.side === "left").length, ph.filter((p) => p.side === "right").length);
177380
+ const titleRightReserve = node.meta?.containerControls === true || node.meta?.internalsHidden === true ? CONTAINER_TITLE_RIGHT_RESERVE : 0;
177016
177381
  const w = Math.max(
177017
177382
  NODE_W,
177018
- titleWidth(node) + 2 * PAD,
177019
- twKind(`\xAB${node.keyword}\xBB`) + 2 * PAD,
177383
+ titleWidth(node) + 2 * PAD + titleRightReserve,
177384
+ twKind(`\xAB${node.keyword}\xBB`) + 2 * PAD + titleRightReserve,
177020
177385
  compartmentWidth(node) + 2 * PAD,
177021
177386
  portsW,
177022
177387
  // issue #108 — an elongated port on the top/bottom edge widens the owner,
@@ -177024,13 +177389,15 @@ function nodeSize(node, direction) {
177024
177389
  portedSideLength(ph, "top"),
177025
177390
  portedSideLength(ph, "bottom")
177026
177391
  );
177027
- const h = Math.max(
177392
+ const featureH = featureCompartmentBandHeight(node);
177393
+ const bodyH = Math.max(
177028
177394
  BOX_H,
177029
- 36 + compartmentHeight(node),
177395
+ featureH > 0 ? 0 : 36 + compartmentHeight(node),
177030
177396
  portRows ? 40 + portRows * 24 + 8 : 0,
177031
177397
  portedSideLength(ph, "left"),
177032
177398
  portedSideLength(ph, "right")
177033
177399
  );
177400
+ const h = bodyH + featureH;
177034
177401
  return { w, h };
177035
177402
  }
177036
177403
  function frameMinSize(f) {
@@ -177038,13 +177405,21 @@ function frameMinSize(f) {
177038
177405
  const sideLabelW = (side) => ports.filter((p) => p.side === side).reduce((m, p) => Math.max(m, tw(p.port.name, 9) + 18), 0);
177039
177406
  const headerW = twKind(`\xAB${f.keyword}\xBB`) + tw(f.label, 11) + 28;
177040
177407
  const portsW = sideLabelW("left") + sideLabelW("right") + 56;
177041
- const w = Math.max(160, headerW, portsW, portedSideLength(ports, "top"), portedSideLength(ports, "bottom"));
177408
+ const featureH = frameFeatureCompartmentHeight(f);
177409
+ const w = Math.max(
177410
+ 160,
177411
+ headerW,
177412
+ portsW,
177413
+ ...featureH > 0 ? [compartmentWidth(f) + 2 * PAD] : [],
177414
+ portedSideLength(ports, "top"),
177415
+ portedSideLength(ports, "bottom")
177416
+ );
177042
177417
  const portRows = Math.max(ports.filter((p) => p.side === "left").length, ports.filter((p) => p.side === "right").length);
177043
177418
  const h = Math.max(
177044
- 58,
177045
- 36 + portRows * 24,
177046
- portedSideLength(ports, "left"),
177047
- portedSideLength(ports, "right")
177419
+ 58 + featureH,
177420
+ 36 + portRows * 24 + featureH,
177421
+ featureH + portedSideLength(ports, "left"),
177422
+ featureH + portedSideLength(ports, "right")
177048
177423
  );
177049
177424
  return { w, h };
177050
177425
  }
@@ -177180,6 +177555,7 @@ function assignPortHandles(ports, overrides) {
177180
177555
  side,
177181
177556
  offset: +(PORT_BAND_START + centre * PORT_BAND_LENGTH).toFixed(4),
177182
177557
  along: alongs[i],
177558
+ rootAlong: alongs[i] ?? endpointGlyphSize(port),
177183
177559
  across: alongs[i] === void 0 ? void 0 : metricsOf(port).across,
177184
177560
  toggleRun: toggleRunOf(port) || void 0
177185
177561
  };
@@ -177211,6 +177587,7 @@ function assignPortHandles(ports, overrides) {
177211
177587
  side: parent.side,
177212
177588
  offset: parent.offset,
177213
177589
  shift: { along: (parent.shift?.along ?? 0) + slot.along, across: slot.across },
177590
+ rootAlong: parent.rootAlong ?? parent.along ?? endpointGlyphSize(parent.port),
177214
177591
  ...hasToggle(kid) ? { along, across, toggleRun: toggleRunOf(kid) } : {}
177215
177592
  };
177216
177593
  nestedHandles.push(handle);
@@ -177220,10 +177597,10 @@ function assignPortHandles(ports, overrides) {
177220
177597
  for (const parent of topHandles) placeChildren(parent);
177221
177598
  return [...topHandles, ...nestedHandles];
177222
177599
  }
177223
- function sizeWithOverride(base, o) {
177600
+ function sizeWithOverride(base, o, minimum = { w: 48, h: 24 }) {
177224
177601
  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
177602
+ w: o?.w !== void 0 ? Math.max(minimum.w, o.w) : base.w,
177603
+ h: o?.h !== void 0 ? Math.max(minimum.h, o.h) : base.h
177227
177604
  };
177228
177605
  }
177229
177606
  function normalizeIvModel(model) {
@@ -177258,6 +177635,7 @@ function promoteIvLeafPartsToFrames(model) {
177258
177635
  const existing = frames[existingIndex];
177259
177636
  frames[existingIndex] = {
177260
177637
  ...existing,
177638
+ layoutKey: n2.layoutKey ?? existing.layoutKey,
177261
177639
  label: n2.name || existing.label,
177262
177640
  isDef: n2.isDef ?? existing.isDef,
177263
177641
  type: n2.type ?? existing.type,
@@ -177275,6 +177653,7 @@ function promoteIvLeafPartsToFrames(model) {
177275
177653
  if (isAnchor || isLeafUsagePart) {
177276
177654
  frames.push({
177277
177655
  id: n2.id,
177656
+ layoutKey: n2.layoutKey,
177278
177657
  label: n2.name,
177279
177658
  keyword: n2.keyword,
177280
177659
  parent: n2.frame,
@@ -177332,13 +177711,11 @@ function applyCaseDefs(model, show) {
177332
177711
  edges
177333
177712
  };
177334
177713
  }
177335
- function collapseIvModel(model, collapsed) {
177714
+ function collapseIvModel(model, hiddenInternals) {
177336
177715
  const frames = model.frames ?? [];
177337
177716
  if (!frames.length) return model;
177338
177717
  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
- );
177718
+ const requested = new Set(frames.filter((frame2) => framePresentationEnabled(hiddenInternals, frame2, true) && isCollapsibleFrame("iv", frame2)).map((frame2) => frame2.id));
177342
177719
  if (requested.size === 0) return model;
177343
177720
  const hasRequestedAncestor = (id2) => {
177344
177721
  let p = frameById.get(id2)?.parent;
@@ -177371,6 +177748,7 @@ function collapseIvModel(model, collapsed) {
177371
177748
  if (!f) return [];
177372
177749
  return [{
177373
177750
  id: f.id,
177751
+ layoutKey: f.layoutKey,
177374
177752
  name: f.label,
177375
177753
  keyword: f.keyword,
177376
177754
  isDef: f.isDef ?? false,
@@ -177381,7 +177759,7 @@ function collapseIvModel(model, collapsed) {
177381
177759
  compartments: f.compartments,
177382
177760
  frame: f.parent,
177383
177761
  source: f.source,
177384
- meta: { ...f.meta, ivCollapsed: true }
177762
+ meta: { ...f.meta, internalsHidden: true }
177385
177763
  }];
177386
177764
  });
177387
177765
  return {
@@ -177394,22 +177772,67 @@ function collapseIvModel(model, collapsed) {
177394
177772
  function isCollapsibleActionFrame(frame2) {
177395
177773
  return frame2.meta?.compositeAction === true && frame2.meta?.anchorFrame !== true;
177396
177774
  }
177775
+ function isCollapsibleStateFrame(frame2) {
177776
+ return frame2.meta?.composite === true && frame2.meta?.anchorFrame !== true;
177777
+ }
177397
177778
  function frameStateKey(frame2) {
177398
177779
  return frame2.layoutKey ?? frame2.id;
177399
177780
  }
177781
+ function framePresentationEnabled(current2, frame2, acceptLegacyDisplayId = false, whenAbsent = false) {
177782
+ const key = frameStateKey(frame2);
177783
+ if (Object.prototype.hasOwnProperty.call(current2, key)) return current2[key] === true;
177784
+ if (acceptLegacyDisplayId && key !== frame2.id && Object.prototype.hasOwnProperty.call(current2, frame2.id)) {
177785
+ return current2[frame2.id] === true;
177786
+ }
177787
+ return whenAbsent;
177788
+ }
177400
177789
  function isCollapsibleFrame(kind, frame2) {
177401
177790
  if (frame2.meta?.anchorFrame === true) return false;
177402
177791
  if (kind === "iv") return frame2.keyword !== "package";
177403
177792
  if (kind === "afv") return isCollapsibleActionFrame(frame2);
177793
+ if (kind === "stv") return isCollapsibleStateFrame(frame2);
177794
+ return false;
177795
+ }
177796
+ function isFeatureCompartmentNode(kind, node) {
177797
+ if (node.meta?.internalsHidden === true) return false;
177798
+ if (node.meta?.boundary === true || node.meta?.boundaryBox === true) return false;
177799
+ if (kind === "iv") return node.shape === "box" && /\b(?:part|item)\b/i.test(node.keyword);
177800
+ if (kind === "afv") return node.shape === "action";
177801
+ if (kind === "stv") return node.shape === "state";
177404
177802
  return false;
177405
177803
  }
177406
- function collapseActionFrames(model, collapsed) {
177804
+ function applyFeatureCompartmentVisibility(model, visibleFeatureCompartments, hiddenInternals = {}) {
177805
+ if (!CONTAINER_PRESENTATION_KINDS.has(model.kind)) return model;
177806
+ const legacy = model.kind === "iv";
177807
+ let changed = false;
177808
+ const project = (host, leaf) => {
177809
+ const visible = framePresentationEnabled(visibleFeatureCompartments, host, legacy, leaf);
177810
+ const compartments = visible ? host.compartments : void 0;
177811
+ const internalsHidden = leaf ? framePresentationEnabled(hiddenInternals, host, legacy) : host.meta?.internalsHidden === true;
177812
+ if (compartments === host.compartments && host.meta?.containerControls === true && host.meta?.featureCompartmentsVisible === visible && (!leaf || host.meta?.internalsHidden === internalsHidden)) return host;
177813
+ changed = true;
177814
+ return {
177815
+ ...host,
177816
+ compartments,
177817
+ meta: {
177818
+ ...host.meta,
177819
+ containerControls: true,
177820
+ featureCompartmentsVisible: visible,
177821
+ ...leaf ? { internalsHidden } : {}
177822
+ }
177823
+ };
177824
+ };
177825
+ const frames = (model.frames ?? []).map((frame2) => isCollapsibleFrame(model.kind, frame2) ? project(frame2, false) : frame2);
177826
+ const nodes = model.nodes.map((node) => isFeatureCompartmentNode(model.kind, node) ? project(node, true) : node);
177827
+ return changed ? { ...model, frames, nodes } : model;
177828
+ }
177829
+ var CONTAINER_PRESENTATION_KINDS = /* @__PURE__ */ new Set(["iv", "afv", "stv"]);
177830
+ function collapseActionFrames(model, hiddenInternals, isHost = isCollapsibleActionFrame, collapsedShape = "action") {
177407
177831
  const frames = model.frames ?? [];
177408
177832
  if (!frames.length) return model;
177409
177833
  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
177834
  const requested = new Set(
177412
- frames.filter((f) => on.has(frameStateKey(f)) && isCollapsibleActionFrame(f)).map((f) => f.id)
177835
+ frames.filter((f) => framePresentationEnabled(hiddenInternals, f) && isHost(f)).map((f) => f.id)
177413
177836
  );
177414
177837
  if (requested.size === 0) return model;
177415
177838
  const rootOf = (id2) => {
@@ -177448,17 +177871,18 @@ function collapseActionFrames(model, collapsed) {
177448
177871
  if (!f) return [];
177449
177872
  return [{
177450
177873
  id: f.id,
177874
+ layoutKey: f.layoutKey,
177451
177875
  name: f.label,
177452
177876
  keyword: f.keyword,
177453
177877
  isDef: f.isDef ?? false,
177454
- shape: "action",
177878
+ shape: collapsedShape,
177455
177879
  type: f.type,
177456
177880
  multiplicity: f.multiplicity,
177457
177881
  ports: f.ports,
177458
177882
  compartments: f.compartments,
177459
177883
  frame: f.parent,
177460
177884
  source: f.source,
177461
- meta: { ...f.meta, actionCollapsed: true }
177885
+ meta: { ...f.meta, internalsHidden: true }
177462
177886
  }];
177463
177887
  });
177464
177888
  const seenEdge = /* @__PURE__ */ new Set();
@@ -177498,8 +177922,13 @@ function modelToFlow(model, opts) {
177498
177922
  model = collapseNestedPorts(model, opts.nestedPorts ?? {});
177499
177923
  if (model.kind === "iv") model = normalizeIvModel(model);
177500
177924
  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);
177925
+ const legacyPresentation = model.kind === "iv" ? opts.ivCompartments : model.kind === "afv" ? opts.collapsedActions : void 0;
177926
+ const hiddenInternals = opts.hiddenInternals ?? legacyPresentation ?? {};
177927
+ const visibleFeatureCompartments = opts.visibleFeatureCompartments ?? legacyPresentation ?? {};
177928
+ model = applyFeatureCompartmentVisibility(model, visibleFeatureCompartments, hiddenInternals);
177929
+ if (model.kind === "iv") model = collapseIvModel(model, hiddenInternals);
177930
+ if (model.kind === "afv") model = collapseActionFrames(model, hiddenInternals);
177931
+ if (model.kind === "stv") model = collapseActionFrames(model, hiddenInternals, isCollapsibleStateFrame, "state");
177503
177932
  if (model.kind === "cv" && opts.subjectBoundary) model = applyCaseBoundary(model);
177504
177933
  const ov = opts.overrides ?? {};
177505
177934
  const nodes = [];
@@ -177528,7 +177957,13 @@ function modelToFlow(model, opts) {
177528
177957
  }
177529
177958
  const orderedFrames = [...frames].sort((a2, b) => frameDepth(a2.id) - frameDepth(b.id));
177530
177959
  for (const f of orderedFrames) {
177531
- const size = sizeWithOverride(frameMinSize(f), ov[f.layoutKey ?? f.id]);
177960
+ const natural = frameMinSize(f);
177961
+ const featureH = frameFeatureCompartmentHeight(f);
177962
+ const size = sizeWithOverride(
177963
+ natural,
177964
+ ov[f.layoutKey ?? f.id],
177965
+ { w: 48, h: featureH > 0 ? natural.h : 24 }
177966
+ );
177532
177967
  nodes.push({
177533
177968
  id: f.id,
177534
177969
  type: "frame",
@@ -177540,6 +177975,8 @@ function modelToFlow(model, opts) {
177540
177975
  ports: assignPortHandles(f.ports, opts.portOverrides),
177541
177976
  w: size.w,
177542
177977
  h: size.h,
177978
+ featureCompartmentHeight: featureH,
177979
+ featureCompartmentMinHeight: featureH > 0 ? natural.h : void 0,
177543
177980
  showPortLabels: opts.showPortLabels,
177544
177981
  showMult: opts.showMult,
177545
177982
  connectPointSpacing,
@@ -177591,8 +178028,16 @@ function modelToFlow(model, opts) {
177591
178028
  for (const n2 of renderModelNodes) {
177592
178029
  const canvasNode = nodeForCanvas(n2);
177593
178030
  const isGeo = model.kind === "gev" && n2.geo !== void 0;
178031
+ const featureH = featureCompartmentBandHeight(n2);
177594
178032
  const base = isGeo ? { w: 24, h: 24 } : nodeSize(canvasNode, opts.direction);
177595
- const size = sizeWithOverride(base, ov[n2.layoutKey ?? n2.id]);
178033
+ const size = sizeWithOverride(
178034
+ base,
178035
+ ov[n2.layoutKey ?? n2.id],
178036
+ {
178037
+ w: canvasNode.meta?.containerControls === true || canvasNode.meta?.internalsHidden === true ? COLLAPSED_CONTAINER_MIN_WIDTH : 48,
178038
+ h: featureH > 0 ? base.h : 24
178039
+ }
178040
+ );
177596
178041
  const parentId = n2.frame ?? (model.kind === "gv" && n2.parent && gvPackageIds.has(n2.parent) ? n2.parent : void 0) ?? (boundary ? boundary.id : void 0);
177597
178042
  nodes.push({
177598
178043
  id: n2.id,
@@ -177605,6 +178050,8 @@ function modelToFlow(model, opts) {
177605
178050
  ports: assignPortHandles(n2.ports, opts.portOverrides),
177606
178051
  w: size.w,
177607
178052
  h: size.h,
178053
+ featureCompartmentHeight: featureH,
178054
+ featureCompartmentMinHeight: featureH > 0 ? base.h : void 0,
177608
178055
  showPortLabels: opts.showPortLabels,
177609
178056
  showMult: opts.showMult,
177610
178057
  connectPointSpacing
@@ -177623,10 +178070,14 @@ function modelToFlow(model, opts) {
177623
178070
  // often nearly the whole visible canvas at typical zoom, so its empty
177624
178071
  // interior must pan the canvas rather than move the part (a click
177625
178072
  // 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" } : {}
178073
+ // never click). Every other diagram kind keeps the whole-body drag.
178074
+ // User report 2026-08-11 the grabbable area was the SVG title TEXT
178075
+ // alone, which a part with hidden internals could barely be moved by
178076
+ // and which carries no move cursor: `.rf-node-header` is the same
178077
+ // full-width title strip a container frame has, so a part behaves the
178078
+ // same whether it draws its internals or not. The text selectors stay
178079
+ // for the title of a node whose header strip is not rendered.
178080
+ ...model.kind === "iv" && !isGeo ? { dragHandle: ".rf-node-header, .dnode-kind, .dnode-label" } : {}
177630
178081
  });
177631
178082
  }
177632
178083
  if (model.kind === "sv") {
@@ -178104,6 +178555,9 @@ async function elkLayout(elk, graph) {
178104
178555
  }
178105
178556
  var FRAME_PAD_TOP = 36;
178106
178557
  var FRAME_PAD = 22;
178558
+ function frameTopPad(node) {
178559
+ return FRAME_PAD_TOP + frameFeatureCompartmentHeight(node.data.frame);
178560
+ }
178107
178561
  var GV_DOCK_FAMILY_ORDER = /* @__PURE__ */ new Map([
178108
178562
  ["owningMembership", 0],
178109
178563
  ["composition", 1],
@@ -178748,7 +179202,7 @@ function buildElkTree(nodes, direction, kind, tree) {
178748
179202
  ...children2.length ? {
178749
179203
  children: children2.map(toElk),
178750
179204
  layoutOptions: {
178751
- "elk.padding": `[top=${FRAME_PAD_TOP},left=${FRAME_PAD},bottom=${FRAME_PAD},right=${FRAME_PAD}]`,
179205
+ "elk.padding": `[top=${frameTopPad(n2)},left=${FRAME_PAD},bottom=${FRAME_PAD},right=${FRAME_PAD}]`,
178752
179206
  ...minimum,
178753
179207
  // REQ-371 — repeat the selected axis on EVERY compound part.
178754
179208
  // ELK does not reliably inherit a root direction into nested
@@ -178854,12 +179308,12 @@ function arrangeIvHierarchy(nodes, direction) {
178854
179308
  children2.push(node);
178855
179309
  byParent.set(key, children2);
178856
179310
  }
178857
- const arrange = (children2, inset) => {
179311
+ const arrange = (children2, insetFor) => {
178858
179312
  if (!children2.length) return;
178859
179313
  const minX = Math.min(...children2.map((child) => child.position.x));
178860
179314
  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;
179315
+ const baseX = insetFor ? FRAME_PAD : 0;
179316
+ const baseY = insetFor ? frameTopPad(insetFor) : 0;
178863
179317
  for (const child of children2) {
178864
179318
  child.position = {
178865
179319
  x: child.position.x - minX + baseX,
@@ -178938,7 +179392,7 @@ function arrangeIvHierarchy(nodes, direction) {
178938
179392
  frame2.data = { ...frame2.data, w: minimum.w, h: minimum.h };
178939
179393
  const children2 = byParent.get(frame2.id) ?? [];
178940
179394
  if (!children2.length) continue;
178941
- arrange(children2, true);
179395
+ arrange(children2, frame2);
178942
179396
  const maxX = Math.max(...children2.map((child) => child.position.x + (child.width ?? child.data.w)));
178943
179397
  const maxY = Math.max(...children2.map((child) => child.position.y + (child.height ?? child.data.h)));
178944
179398
  const width = Math.max(minimum.w, maxX + FRAME_PAD);
@@ -178947,7 +179401,7 @@ function arrangeIvHierarchy(nodes, direction) {
178947
179401
  frame2.height = height;
178948
179402
  frame2.data = { ...frame2.data, w: width, h: height };
178949
179403
  }
178950
- arrange(byParent.get(void 0) ?? [], false);
179404
+ arrange(byParent.get(void 0) ?? [], void 0);
178951
179405
  }
178952
179406
  function applyElkPositions(node, byId) {
178953
179407
  for (const child of node.children ?? []) {
@@ -179066,7 +179520,13 @@ function nextSideAnchor(counts, offsetsCache, node, side, spacing, demand) {
179066
179520
  let offsets = offsetsCache.get(key);
179067
179521
  if (!offsets) {
179068
179522
  const derived = sidePointOffsetsForLength(
179069
- sideEffectiveLength(node.data.node?.shape, side, node.data.w, node.data.h),
179523
+ sideEffectiveLength(
179524
+ node.data.node?.shape,
179525
+ side,
179526
+ node.data.w,
179527
+ node.data.h,
179528
+ featureBandTopReserve(node.data)
179529
+ ),
179070
179530
  spacing
179071
179531
  );
179072
179532
  const needed = demand?.get(key) ?? 0;
@@ -179078,7 +179538,13 @@ function nextSideAnchor(counts, offsetsCache, node, side, spacing, demand) {
179078
179538
  }
179079
179539
  const offset2 = offsets[i % offsets.length];
179080
179540
  if (i < offsets.length && !sidePointOffsetsForLength(
179081
- sideEffectiveLength(node.data.node?.shape, side, node.data.w, node.data.h),
179541
+ sideEffectiveLength(
179542
+ node.data.node?.shape,
179543
+ side,
179544
+ node.data.w,
179545
+ node.data.h,
179546
+ featureBandTopReserve(node.data)
179547
+ ),
179082
179548
  spacing
179083
179549
  ).some((o) => Math.abs(o - offset2) < 1e-6)) {
179084
179550
  const explicit = node.data.explicitSideAnchors?.[side] ?? [];
@@ -189511,7 +189977,7 @@ function dockPoint(node, handleId, byId, fallback) {
189511
189977
  const ph = node.data.ports?.find((p) => p.port.id === port.portId);
189512
189978
  if (ph) {
189513
189979
  const dockSide = clampDockSide(port.side, ph.side);
189514
- const rect = portGlyphRect(ph, w, h);
189980
+ const rect = portGlyphRect(ph, w, h, featureBandTopReserve(node.data));
189515
189981
  const n2 = DOCK_NORMAL[dockSide];
189516
189982
  const at = portDockAt(rect, ph.side, dockSide);
189517
189983
  return { point: { x: pos.x + at.x, y: pos.y + at.y }, normal: n2 };
@@ -189519,12 +189985,19 @@ function dockPoint(node, handleId, byId, fallback) {
189519
189985
  }
189520
189986
  const structural = node.data.structuralDocks?.find((dock) => dock.id === handleId);
189521
189987
  if (structural) {
189522
- const c = sidePoint(structural.side, structural.offset, w, h);
189988
+ const c = sidePoint(structural.side, structural.offset, w, h, featureBandTopReserve(node.data));
189523
189989
  return { point: { x: pos.x + c.x, y: pos.y + c.y }, normal: DOCK_NORMAL[structural.side] };
189524
189990
  }
189525
189991
  const sideAnchor = parseSideAnchorHandleId(handleId);
189526
189992
  if (sideAnchor) {
189527
- const c = sideSurfacePoint(node.data.node?.shape, sideAnchor.side, sideAnchor.offset, w, h);
189993
+ const c = sideSurfacePoint(
189994
+ node.data.node?.shape,
189995
+ sideAnchor.side,
189996
+ sideAnchor.offset,
189997
+ w,
189998
+ h,
189999
+ featureBandTopReserve(node.data)
190000
+ );
189528
190001
  return { point: { x: pos.x + c.x, y: pos.y + c.y }, normal: DOCK_NORMAL[sideAnchor.side] };
189529
190002
  }
189530
190003
  return { point: fallback };
@@ -189607,7 +190080,9 @@ function paintedRouteForEdge(edge, nodes, byId, edges = [], lineStyle = "orthogo
189607
190080
  return { points: manhattanThroughPoints([sd.point, ...route, td.point], sd.normal), corner: 0 };
189608
190081
  }
189609
190082
  const structural = isStructuralBusEdge(edge);
189610
- const [sp, tp] = fanApart(sd.point, td.point, parallelEdgeOffset(
190083
+ const sp = sd.point;
190084
+ const tp = td.point;
190085
+ const via = fanVia(sp, tp, parallelEdgeOffset(
189611
190086
  edge.id,
189612
190087
  edge.source,
189613
190088
  edge.target,
@@ -189615,6 +190090,9 @@ function paintedRouteForEdge(edge, nodes, byId, edges = [], lineStyle = "orthogo
189615
190090
  edge.targetHandle,
189616
190091
  edges
189617
190092
  ));
190093
+ if (via.length) {
190094
+ return { points: manhattanThroughPoints([sp, ...via, tp], sd.normal), corner: 0 };
190095
+ }
189618
190096
  if (edgeUsesObstacleRouting(edge.data?.kind, lineStyle, structural)) {
189619
190097
  return {
189620
190098
  points: routeOrthogonalAroundRects(sp, tp, obstacleRects(nodes, edge.source, edge.target, byId, scene), {
@@ -189642,7 +190120,7 @@ function geometrySignature(nodes) {
189642
190120
  if (n2.dragging === true || n2.resizing === true) continue;
189643
190121
  out += `${n2.id}:${n2.parentId ?? ""}:${n2.type ?? ""}:${n2.hidden ? 1 : 0}:`;
189644
190122
  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}`;
190123
+ out += `:${n2.data.node?.shape ?? ""}:${n2.data.isBoundary ? 1 : 0}:${n2.data.featureCompartmentHeight ?? 0}`;
189646
190124
  for (const p of n2.data.ports ?? []) {
189647
190125
  out += `|${p.port.id},${p.side},${p.offset},${p.along ?? ""},${p.shift?.along ?? ""},${p.shift?.across ?? ""}`;
189648
190126
  }
@@ -189762,12 +190240,15 @@ function parallelOffsetMap(edges) {
189762
190240
  const groups = /* @__PURE__ */ new Map();
189763
190241
  for (const edge of edges) {
189764
190242
  if (isPortDocked(edge.sourceHandle) || isPortDocked(edge.targetHandle)) continue;
189765
- const [a2, b] = edge.source <= edge.target ? [edge.source, edge.target] : [edge.target, edge.source];
190243
+ const forward = edge.source <= edge.target;
190244
+ const [a2, b] = forward ? [edge.source, edge.target] : [edge.target, edge.source];
190245
+ const [ha, hb] = forward ? [edge.sourceHandle ?? "", edge.targetHandle ?? ""] : [edge.targetHandle ?? "", edge.sourceHandle ?? ""];
189766
190246
  let byTarget = groups.get(a2);
189767
190247
  if (!byTarget) groups.set(a2, byTarget = /* @__PURE__ */ new Map());
189768
- const group = byTarget.get(b) ?? [];
190248
+ const key = `${b}\0${ha}\0${hb}`;
190249
+ const group = byTarget.get(key) ?? [];
189769
190250
  group.push(edge);
189770
- byTarget.set(b, group);
190251
+ byTarget.set(key, group);
189771
190252
  }
189772
190253
  cached = /* @__PURE__ */ new Map();
189773
190254
  for (const byTarget of groups.values()) {
@@ -189788,14 +190269,14 @@ function parallelEdgeOffset(id2, _source, _target, sourceHandle, targetHandle, e
189788
190269
  if (isPortDocked(sourceHandle) || isPortDocked(targetHandle)) return 0;
189789
190270
  return parallelOffsetMap(edges).get(id2) ?? 0;
189790
190271
  }
189791
- function fanApart(sp, tp, off) {
190272
+ function fanVia(sp, tp, off) {
189792
190273
  const dx = tp.x - sp.x;
189793
190274
  const dy = tp.y - sp.y;
189794
190275
  const len = Math.hypot(dx, dy);
189795
- if (!len || !off) return [sp, tp];
190276
+ if (!len || !off) return [];
189796
190277
  const nx = -dy / len;
189797
190278
  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 }];
190279
+ return [{ x: (sp.x + tp.x) / 2 + nx * off, y: (sp.y + tp.y) / 2 + ny * off }];
189799
190280
  }
189800
190281
  var GV_BUS_EDGE_KINDS = /* @__PURE__ */ new Set([
189801
190282
  "owningMembership",
@@ -189845,13 +190326,14 @@ function computeEdgePath(args) {
189845
190326
  live ? { x: live.targetX, y: live.targetY } : nodeCentre(targetNode, byId)
189846
190327
  );
189847
190328
  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];
190329
+ const sp = sd.point;
190330
+ const tp = td.point;
190331
+ const via = route?.length ? route : fanVia(sp, tp, parallelEdgeOffset(edge.id, edge.source, edge.target, edge.sourceHandle, edge.targetHandle, edges));
190332
+ const anchors = [sp, ...via, tp];
189851
190333
  const common = { sourceX: sp.x, sourceY: sp.y, targetX: tp.x, targetY: tp.y };
189852
190334
  const structural = isStructuralBusEdge(edge);
189853
190335
  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 } : (() => {
190336
+ 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
190337
  const pts = manhattanThroughPoints(anchors, sd.normal);
189856
190338
  return { path: pointsToPath(pts), label: pathMidpoint(pts), points: pts };
189857
190339
  })() : void 0;
@@ -189869,8 +190351,8 @@ function computeEdgePath(args) {
189869
190351
  normal: sd.normal
189870
190352
  };
189871
190353
  }
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;
190354
+ const autoRouted = wantsObstacles && !guided && !args.dragging ? routedOrthogonalPath({ point: sp, normal: sd.normal }, { point: tp, normal: td.normal }, nodes, edge.source, edge.target) : void 0;
190355
+ const routed = guided ?? autoRouted;
189874
190356
  if (routed) {
189875
190357
  return {
189876
190358
  path: routed.path,
@@ -189930,6 +190412,13 @@ function markersFor(kind) {
189930
190412
  case "redefinition":
189931
190413
  case "referenceSubsetting":
189932
190414
  return { end: "url(#tri-hollow)" };
190415
+ // REQ-160 — temporal decomposition (OMG 8.2.3.9): an occurrence's life →
190416
+ // one `timeslice`/`snapshot` PORTION of it. A portion specializes the life
190417
+ // it is part of, so it carries the specialization triangle — at the WHOLE
190418
+ // end, which is where the edge starts, so the marker is a start-oriented
190419
+ // twin of `tri-hollow` rather than the end-oriented one.
190420
+ case "portion":
190421
+ return { start: "url(#tri-hollow-start)" };
189933
190422
  case "flow":
189934
190423
  case "message":
189935
190424
  return { end: "url(#arrow-cyan)" };
@@ -190250,6 +190739,10 @@ var MARKER_DEFS_SVG = [
190250
190739
  `<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
190740
  `<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
190741
  `<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>`,
190742
+ // REQ-160 — the portion (temporal-decomposition) triangle sits at the START of
190743
+ // the edge, at the whole occurrence. Same shape as `tri-hollow`, mirrored so
190744
+ // `orient="auto"` points it back along the line instead of down it.
190745
+ `<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
190746
  `<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
190747
  `<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
190748
  `<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 +190756,7 @@ var KIN_MARKER_DEFS_SVG = [
190263
190756
  `<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
190757
  `<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
190758
  `<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>`,
190759
+ `<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
190760
  `<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
190761
  `<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
190762
  `<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 +190902,9 @@ body {
190408
190902
  * reading. */
190409
190903
  .dframe-rect { fill: var(--node-surface); fill-opacity: 0.92; stroke: var(--fg-3); stroke-width: 1.2; }
190410
190904
  .dframe-rect.container { fill-opacity: 0.16; }
190905
+ /* REQ-366/386 \u2014 an expanded frame remains translucent around its graphical
190906
+ * internals, but its optional bottom feature list is an opaque readable card. */
190907
+ .dfeature-band-bg { fill: var(--node-surface); }
190411
190908
  .dframe-rect.package.library { stroke-dasharray: 5 3; }
190412
190909
  .dframe-rect.selected { stroke: var(--accent); stroke-width: 2; }
190413
190910
  /* RULE (diagram-rules \u2014 Frame): parallel composite-state region \u2014 dashed border. */
@@ -190452,17 +190949,6 @@ body {
190452
190949
  .dnode-shape.decision { stroke: var(--yellow); }
190453
190950
  /* REQ-006 \u2014 selected-node resize grip */
190454
190951
  .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
190952
  /* edges (REQ-186) */
190467
190953
  .dlink { stroke: var(--fg-2); stroke-width: 1.2; fill: none; }
190468
190954
  .dlink.flow, .dlink.connect, .dlink.message, .dlink.interface { stroke: var(--cyan); }
@@ -190483,6 +190969,9 @@ body {
190483
190969
  }
190484
190970
  .dlink.binding { stroke: var(--yellow); stroke-width: 2.6; } /* REQ-195 \u2014 "=" binding (double-weight line) */
190485
190971
  .dlink.specialization, .dlink.owningMembership, .dlink.composition, .dlink.association { stroke: var(--fg-2); }
190972
+ /* REQ-160 \u2014 temporal decomposition of an occurrence into timeslices/snapshots
190973
+ (OMG 8.2.3.9): a solid structural line, like the specialization it is. */
190974
+ .dlink.portion { stroke: var(--fg-2); }
190486
190975
  /* REQ-094/190 \u2014 custom bound-reference overlay. A dotted directed line is
190487
190976
  deliberately distinct from solid feature membership and its owner diamond. */
190488
190977
  .dlink.reference { stroke: var(--fg-2); stroke-dasharray: 1 3; stroke-linecap: round; }
@@ -190842,16 +191331,23 @@ body {
190842
191331
  /* REQ-190 \u2014 the direction, General-View mode, and line-style segs are icon-only:
190843
191332
  tighter padding (see issue 146). */
190844
191333
  .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 {
191334
+ .seg.seg-visibility button, .seg.seg-presentation button { padding: 2px 6px; }
191335
+ /* REQ-196/200/366/386 \u2014 every visibility/presentation segment shares one
191336
+ treatment: visible is lit; hidden is dimmed and slashed. Semantic icon hues
191337
+ remain visible instead of being replaced by a solid orange active button. */
191338
+ .seg.seg-visibility button.on, .seg.seg-presentation button.on {
191339
+ background: var(--bg-active); color: var(--fg-0);
191340
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg-2) 30%, transparent); }
191341
+ .seg.seg-visibility button:not(.on), .seg.seg-presentation button:not(.on) { opacity: 0.38; }
191342
+ .seg.seg-visibility button:not(.on)::after, .seg.seg-presentation button:not(.on)::after {
190851
191343
  content: ''; position: absolute; left: 4px; right: 4px; top: 50%;
190852
- border-top: 1.2px solid currentColor; transform: rotate(-20deg);
191344
+ border-top: 1.2px solid var(--red, #e07a7a); transform: rotate(-20deg);
190853
191345
  }
190854
- .seg.seg-afv-filters button { position: relative; }
191346
+ .seg.seg-visibility button, .seg.seg-presentation button { position: relative; }
191347
+ .visibility-icon.tone-structure, .presentation-icon.tone-structure { color: var(--cyan); }
191348
+ .visibility-icon.tone-behavior, .presentation-icon.tone-behavior { color: var(--accent); }
191349
+ .visibility-icon.tone-definition, .presentation-icon.tone-definition,
191350
+ .definition-visibility-icon { color: var(--violet); }
190855
191351
  /* issue 156 \u2014 General View quick filters: one icon chip per element category
190856
191352
  (parts always show). A chip stays lit in its element-type colour while its
190857
191353
  category is VISIBLE; hiding one dims and slashes it so an engaged filter is
@@ -191089,6 +191585,12 @@ svg.react-flow__connectionline { z-index: 1; }
191089
191585
  .rf-frame .rf-node-svg { pointer-events: none; }
191090
191586
  .rf-frame .dframe-rect { pointer-events: none; }
191091
191587
  .rf-frame-header { position: absolute; pointer-events: auto; cursor: move; }
191588
+ /* issue #58 / user report 2026-08-11 \u2014 the matching title strip on an
191589
+ * Interconnection View PART node. It is what the node's dragHandle selects, so
191590
+ * a part reads and moves identically whether it draws its internals (a frame) or
191591
+ * has them hidden (a node) \u2014 and shows the same move cursor either way, instead
191592
+ * of React Flow's grab hand over a body that cannot actually be dragged. */
191593
+ .rf-node-header { position: absolute; pointer-events: auto; cursor: move; }
191092
191594
  /* REQ-379 \u2014 the frame's BORDER band is hover-sensitive (the interior stays
191093
191595
  * click-through): hovering it reveals and mounts the frame's side connect points,
191094
191596
  * which are drawn on that same border. Stroke-only hit testing, so nothing but the
@@ -191096,7 +191598,7 @@ svg.react-flow__connectionline { z-index: 1; }
191096
191598
  .dframe-hover-band { stroke-width: 12; pointer-events: stroke; }
191097
191599
  .rf-frame .dport-hit { pointer-events: auto; }
191098
191600
  .rf-frame .react-flow__handle { pointer-events: auto; }
191099
- .rf-frame .div-part-toggle { pointer-events: auto; }
191601
+ .rf-frame .dcontainer-controls { pointer-events: auto; }
191100
191602
  .rf-frame .react-flow__resize-control { pointer-events: auto; }
191101
191603
  .rf-node-svg { display: block; overflow: visible; }
191102
191604
  .react-flow__node.selected { box-shadow: none; }
@@ -191293,12 +191795,29 @@ svg.react-flow__connectionline { z-index: 1; }
191293
191795
  color: var(--fg-0); background: var(--bg-1); border: 1px solid var(--line-soft); border-radius: 3px; padding: 0 4px; white-space: nowrap; }
191294
191796
  .rf-edge-label.mult { background: transparent; border: 0; color: var(--fg-2); padding: 0; }
191295
191797
 
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;
191798
+ /* REQ-366/386 \u2014 two adjacent, independent container presentation controls.
191799
+ * +/\u2212 owns graphical internals; the outlined list owns textual feature
191800
+ * compartments. Their compact hit targets stay interactive over frame chrome.
191801
+ * Both marks are STROKED SVG sized to the whole button (nodes.tsx
191802
+ * ContainerControlGlyph), so nothing here can shift them off centre \u2014 a typed
191803
+ * +/\u2212 character and a bordered box drawn in CSS both did (user report
191804
+ * 2026-08-11). */
191805
+ /* The inset is set inline per shape (nodes.tsx ContainerControls): a rounded
191806
+ * outline needs the pair pulled in from its corner arc, a sharp one does not.
191807
+ * These values are the sharp-corner default. */
191808
+ .dcontainer-controls { position: absolute; right: ${CONTAINER_CONTROLS_RIGHT}px; top: ${CONTAINER_CONTROLS_RIGHT}px; z-index: 8;
191809
+ display: inline-flex; align-items: center; gap: ${CONTAINER_CONTROL_GAP}px; pointer-events: auto; }
191810
+ /* The BUTTON is only a hit target: no plate, no fill, in any state (user
191811
+ * direction 2026-08-11 \u2014 "remove that translucent thing surrounding the box I
191812
+ * just want the hamburger menu"). Neither does STATE live here: the mark itself
191813
+ * carries it \u2014 an empty box or a filled one, a + or a \u2212, at one resting colour
191814
+ * ("at best don't even change color"). Only the pointer feedback is a colour. */
191815
+ .div-part-toggle, .dfeature-toggle { width: ${CONTAINER_CONTROL_SIZE}px; height: ${CONTAINER_CONTROL_SIZE}px;
191816
+ padding: 0; margin: 0; border: 0; background: none; cursor: pointer; color: var(--fg-1);
191300
191817
  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; }
191818
+ .div-part-toggle svg, .dfeature-toggle svg { display: block; }
191819
+ .div-part-toggle:hover, .div-part-toggle:active, .div-part-toggle:focus-visible,
191820
+ .dfeature-toggle:hover, .dfeature-toggle:active, .dfeature-toggle:focus-visible { color: var(--accent); outline: none; }
191302
191821
 
191303
191822
  /* React Flow chrome theming (controls / minimap / attribution) */
191304
191823
  .react-flow__controls { box-shadow: 0 2px 10px rgba(0,0,0,0.35); }
@@ -191395,7 +191914,7 @@ function buildDiagramSvg(options) {
191395
191914
  const { w, h } = sizeOf(node);
191396
191915
  extend2(p.x, p.y, w, h);
191397
191916
  for (const ph of node.data.ports ?? []) {
191398
- const rect = portGlyphRect(ph, w, h);
191917
+ const rect = portGlyphRect(ph, w, h, featureBandTopReserve(node.data));
191399
191918
  extend2(p.x + rect.x, p.y + rect.y, rect.width, rect.height);
191400
191919
  if (!node.data.showPortLabels || rect.elongated) continue;
191401
191920
  const text = `${ph.port.conjugated ? "~" : ""}${ph.port.name}`;
@@ -191538,7 +192057,7 @@ function tableToCsv(table2, order) {
191538
192057
  // ../extension/src/diagram-sidecar.ts
191539
192058
  var import_diagram_protocol2 = __toESM(require_out());
191540
192059
  var FILE_OVERVIEW_ANCHOR = "~file";
191541
- var SIDECAR_VERSION = 3;
192060
+ var SIDECAR_VERSION = 4;
191542
192061
  function emptySideCar() {
191543
192062
  return { version: SIDECAR_VERSION, anchors: {} };
191544
192063
  }
@@ -191593,6 +192112,20 @@ function dropOrdinalKeys(map3) {
191593
192112
  }
191594
192113
  return Object.keys(kept).length > 0 ? kept : void 0;
191595
192114
  }
192115
+ function enabledEntries(value) {
192116
+ if (!value || typeof value !== "object") return {};
192117
+ return Object.fromEntries(Object.entries(value).filter(([, enabled]) => enabled === true));
192118
+ }
192119
+ function normalizeContainerPresentation(state, kind) {
192120
+ const legacy = state;
192121
+ const coupled = kind === "iv" ? enabledEntries(legacy.ivCompartments) : kind === "afv" ? enabledEntries(legacy.collapsedActions) : {};
192122
+ if (Object.keys(coupled).length > 0) {
192123
+ state.hiddenInternals = { ...coupled, ...state.hiddenInternals ?? {} };
192124
+ state.visibleFeatureCompartments = { ...coupled, ...state.visibleFeatureCompartments ?? {} };
192125
+ }
192126
+ delete legacy.ivCompartments;
192127
+ delete legacy.collapsedActions;
192128
+ }
191596
192129
  function normalizeSideCar(value) {
191597
192130
  if (!value || typeof value !== "object") return emptySideCar();
191598
192131
  const v = value;
@@ -191601,16 +192134,36 @@ function normalizeSideCar(value) {
191601
192134
  if (version >= SIDECAR_VERSION) {
191602
192135
  for (const byKind of Object.values(anchors)) {
191603
192136
  if (!byKind || typeof byKind !== "object") continue;
191604
- for (const state of Object.values(byKind)) {
192137
+ for (const [kind, state] of Object.entries(byKind)) {
191605
192138
  if (!state || typeof state !== "object") continue;
191606
192139
  const dir = sanitizeDirection(state.direction);
191607
192140
  if (dir === void 0) delete state.direction;
191608
192141
  else state.direction = dir;
191609
192142
  normalizeGridFields(state);
192143
+ normalizeContainerPresentation(state, kind);
191610
192144
  }
191611
192145
  }
191612
192146
  return { version, anchors };
191613
192147
  }
192148
+ if (version === 3) {
192149
+ const migrated2 = {};
192150
+ for (const [anchor, byKind] of Object.entries(anchors)) {
192151
+ if (!byKind || typeof byKind !== "object") continue;
192152
+ const nextByKind = {};
192153
+ for (const [kind, raw] of Object.entries(byKind)) {
192154
+ if (!raw || typeof raw !== "object") continue;
192155
+ const state = { ...raw };
192156
+ normalizeGridFields(state);
192157
+ const dir = sanitizeDirection(state.direction);
192158
+ if (dir === void 0) delete state.direction;
192159
+ else state.direction = dir;
192160
+ normalizeContainerPresentation(state, kind);
192161
+ nextByKind[kind] = state;
192162
+ }
192163
+ migrated2[anchor] = nextByKind;
192164
+ }
192165
+ return { version: SIDECAR_VERSION, anchors: migrated2 };
192166
+ }
191614
192167
  if (version === 2) {
191615
192168
  const migrated2 = {};
191616
192169
  for (const [anchor, byKind] of Object.entries(anchors)) {
@@ -191629,6 +192182,7 @@ function normalizeSideCar(value) {
191629
192182
  const ports = dropOrdinalKeys(state.ports);
191630
192183
  if (ports) state.ports = ports;
191631
192184
  else delete state.ports;
192185
+ normalizeContainerPresentation(state, kind);
191632
192186
  nextByKind[kind] = state;
191633
192187
  }
191634
192188
  migrated2[anchor] = nextByKind;
@@ -191643,14 +192197,17 @@ function normalizeSideCar(value) {
191643
192197
  if (!raw || typeof raw !== "object") continue;
191644
192198
  const state = raw;
191645
192199
  normalizeGridFields(state);
192200
+ normalizeContainerPresentation(state, kind);
191646
192201
  const next = {};
191647
192202
  if (state.mode !== void 0) next.mode = state.mode;
191648
192203
  if (state.zoom !== void 0) next.zoom = state.zoom;
191649
192204
  const dir = sanitizeDirection(state.direction);
191650
192205
  if (dir !== void 0) next.direction = dir;
191651
- if (state.ivCompartments !== void 0) next.ivCompartments = state.ivCompartments;
192206
+ if (state.hiddenInternals !== void 0) next.hiddenInternals = state.hiddenInternals;
192207
+ if (state.visibleFeatureCompartments !== void 0) {
192208
+ next.visibleFeatureCompartments = state.visibleFeatureCompartments;
192209
+ }
191652
192210
  if (state.nestedPorts !== void 0) next.nestedPorts = state.nestedPorts;
191653
- if (state.collapsedActions !== void 0) next.collapsedActions = state.collapsedActions;
191654
192211
  if (state.gvMode !== void 0) next.gvMode = state.gvMode;
191655
192212
  if (state.gvFilters !== void 0) next.gvFilters = state.gvFilters;
191656
192213
  if (state.lineStyle !== void 0) next.lineStyle = state.lineStyle;
@@ -191726,6 +192283,7 @@ function edgeKindLabel(kind) {
191726
192283
  if (kind === "composition") return "feature membership";
191727
192284
  if (kind === "owningMembership") return "owning membership";
191728
192285
  if (kind === "definedBy") return "defined by";
192286
+ if (kind === "portion") return "temporal decomposition (portion)";
191729
192287
  return kind;
191730
192288
  }
191731
192289
 
@@ -192374,7 +192932,8 @@ function nodeSidePointOffsets(data, side, connectable) {
192374
192932
  data.h,
192375
192933
  data.connectPointSpacing ?? CONNECT_POINT_SPACING_DEFAULT,
192376
192934
  connectable,
192377
- data.node?.shape
192935
+ data.node?.shape,
192936
+ data.featureCompartmentHeight ?? 0
192378
192937
  );
192379
192938
  const explicit = data.explicitSideAnchors?.[side] ?? [];
192380
192939
  return [.../* @__PURE__ */ new Set([...derived, ...explicit])].sort((a2, b) => a2 - b);
@@ -192405,7 +192964,7 @@ var SIDE_NORMAL = {
192405
192964
  left: { x: -1, y: 0 }
192406
192965
  };
192407
192966
  var clampOffset = (v) => Math.max(0.06, Math.min(0.94, v));
192408
- function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192967
+ function usePortDrag(containerRef, w, h, topReserve, onPortMove, onPortPreview, onSelect) {
192409
192968
  const [dragId, setDragId] = (0, import_react8.useState)(void 0);
192410
192969
  const onPortPointerDown = (portId, e, draggable = true) => {
192411
192970
  if (e.button !== 0) return;
@@ -192422,12 +192981,8 @@ function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192422
192981
  const placement = (cx, cy) => {
192423
192982
  const lx = rect.width ? (cx - rect.left) / rect.width * w : 0;
192424
192983
  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) };
192984
+ const nearest = nearestPortOwnerPlacement(lx, ly, w, h, topReserve);
192985
+ return { side: nearest.side, offset: clampOffset(nearest.offset) };
192431
192986
  };
192432
192987
  const move = (ev) => {
192433
192988
  if (!moved && Math.hypot(ev.clientX - sx, ev.clientY - sy) < 4) return;
@@ -192450,18 +193005,6 @@ function usePortDrag(containerRef, w, h, onPortMove, onPortPreview, onSelect) {
192450
193005
  };
192451
193006
  return { dragId, onPortPointerDown };
192452
193007
  }
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
193008
  function DualHandle({ id: id2, side, x, y, kind, active, connectable = true, startable = false, size }) {
192466
193009
  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
193010
  const cls = `rf-handle rf-handle-${kind}${active ? " active" : ""}`;
@@ -192512,16 +193055,22 @@ function fitKind(s, availPx) {
192512
193055
  function FullTitle({ full, shown }) {
192513
193056
  return shown === full ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: full });
192514
193057
  }
192515
- function compartmentTops(node, top = 30) {
193058
+ function compartmentTops(node, top = NODE_COMPARTMENT_TOP) {
192516
193059
  const comps = node.compartments ?? [];
192517
193060
  const tops = [];
192518
193061
  let acc = top;
192519
193062
  for (const c of comps) {
192520
193063
  tops.push(acc);
192521
- acc += 16 + c.items.length * 16;
193064
+ acc += compartmentBlockHeight(c.items.length);
192522
193065
  }
192523
193066
  return { tops, comps };
192524
193067
  }
193068
+ function containerBandBottom(node, top) {
193069
+ return top + (node.compartments ?? []).reduce((h, c) => h + compartmentBlockHeight(c.items.length), 0);
193070
+ }
193071
+ function ContainerBandRule({ w, y }) {
193072
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { className: "dcompartment-rule closing", x1: 0, y1: y, x2: w, y2: y });
193073
+ }
192525
193074
  function Compartments({ node, w, top }) {
192526
193075
  const { tops, comps } = compartmentTops(node, top);
192527
193076
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: comps.map((c, ci) => {
@@ -192529,13 +193078,13 @@ function Compartments({ node, w, top }) {
192529
193078
  const title = fitKind(c.title, w - 16);
192530
193079
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { className: "dnode-compartment", children: [
192531
193080
  /* @__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: [
193081
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 8, y: cy + COMPARTMENT_TITLE_BASELINE, children: [
192533
193082
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: c.title, shown: title }),
192534
193083
  title
192535
193084
  ] }),
192536
193085
  c.items.map((it, i) => {
192537
193086
  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: [
193087
+ 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
193088
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: it.text, shown }),
192540
193089
  shown
192541
193090
  ] }, i);
@@ -192548,37 +193097,53 @@ function variabilityMark(keyword) {
192548
193097
  if (/^variant\b/.test(keyword)) return " \u25C6";
192549
193098
  return "";
192550
193099
  }
193100
+ function nodeShowsControls(node) {
193101
+ return node.meta?.containerControls === true;
193102
+ }
193103
+ function titleNameLine(name, type, mult, alias, availPx) {
193104
+ const suffixChars = (mult ? mult.length + 1 : 0) + (alias ? alias.length + 9 : 0);
193105
+ const budget = Math.max(6, fitChars(availPx, 11) - suffixChars);
193106
+ let nameShown = name;
193107
+ let typeShown = type;
193108
+ if (type) {
193109
+ if (name.length + 3 + type.length > budget) {
193110
+ const nameMax = Math.max(4, Math.min(name.length, Math.ceil(budget * 0.6)));
193111
+ nameShown = clipText(name, nameMax);
193112
+ typeShown = clipText(type, Math.max(3, budget - nameShown.length - 3));
193113
+ }
193114
+ } else {
193115
+ nameShown = clipText(name, budget);
193116
+ }
193117
+ return {
193118
+ nameShown,
193119
+ typeShown,
193120
+ full: `${name}${type ? ` : ${type}` : ""}`,
193121
+ shown: `${nameShown}${typeShown ? ` : ${typeShown}` : ""}`
193122
+ };
193123
+ }
192551
193124
  function BoxBody({ node, w, h, showMult }) {
192552
- const rx = node.isDef ? 0 : 14;
193125
+ const rx = nodeCornerRadius(node.shape, node.isDef, h, false);
192553
193126
  const cls = node.keyword.startsWith("requirement") ? "req" : "";
192554
- const cx = w / 2;
193127
+ const titleArea = containerTitleArea(w, nodeShowsControls(node));
193128
+ const cx = titleArea.centerX;
192555
193129
  const kindFull = `\xAB${node.keyword}\xBB${variabilityMark(node.keyword)}`;
192556
- const kindShown = fitKind(kindFull, w - 8);
192557
- const avail = Math.max(24, w - 12);
193130
+ const kindShown = fitKind(kindFull, Math.max(24, titleArea.width - 8));
192558
193131
  const mult = showMult && node.multiplicity ? node.multiplicity : void 0;
192559
193132
  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}` : ""}`;
193133
+ const { nameShown, typeShown, full: fullTitle, shown: shownTitle } = titleNameLine(
193134
+ node.name,
193135
+ node.type,
193136
+ mult,
193137
+ alias,
193138
+ Math.max(24, titleArea.width - 12)
193139
+ );
192575
193140
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
192576
193141
  /* @__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: [
193142
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: CONTAINER_TITLE_KIND_BASELINE, textAnchor: "middle", children: [
192578
193143
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192579
193144
  kindShown
192580
193145
  ] }),
192581
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: `dnode-label ${node.abstract ? "abstract" : ""}`, x: cx, y: 28, textAnchor: "middle", children: [
193146
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: `dnode-label ${node.abstract ? "abstract" : ""}`, x: cx, y: CONTAINER_TITLE_NAME_BASELINE, textAnchor: "middle", children: [
192582
193147
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: fullTitle, shown: shownTitle }),
192583
193148
  nameShown,
192584
193149
  typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
@@ -192594,41 +193159,57 @@ function BoxBody({ node, w, h, showMult }) {
192594
193159
  alias
192595
193160
  ] }) : ""
192596
193161
  ] }),
192597
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w })
193162
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w }),
193163
+ nodeShowsControls(node) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: containerBandBottom(node, NODE_COMPARTMENT_TOP) }) : null
192598
193164
  ] });
192599
193165
  }
192600
- function RoundedBody({ node, w, h, cls }) {
192601
- const r = node.isDef ? 0 : Math.min(h / 2, 20);
193166
+ function RoundedBody({ node, w, h, cls, showMult }) {
192602
193167
  const comps = node.compartments ?? [];
193168
+ const r = nodeCornerRadius(node.shape, node.isDef, h, comps.length > 0);
192603
193169
  const composite = node.meta?.composite === true ? " composite" : "";
192604
193170
  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);
193171
+ const titleArea = containerTitleArea(w, nodeShowsControls(node));
193172
+ const cx = titleArea.centerX;
193173
+ const kindShown = fitKind(kindFull, Math.max(24, titleArea.width - 8));
193174
+ const mult = showMult && node.multiplicity ? node.multiplicity : void 0;
193175
+ const { nameShown, typeShown, full: fullTitle, shown: shownTitle } = titleNameLine(
193176
+ node.name,
193177
+ node.type,
193178
+ mult,
193179
+ void 0,
193180
+ Math.max(24, titleArea.width - 12)
193181
+ );
193182
+ const nameLine = (y) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: cx, y, textAnchor: "middle", children: [
193183
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: fullTitle, shown: shownTitle }),
193184
+ nameShown,
193185
+ typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
193186
+ " : ",
193187
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tspan", { className: "dnode-type", children: typeShown })
193188
+ ] }) : "",
193189
+ mult ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tspan", { className: "dnode-kind", children: [
193190
+ " ",
193191
+ mult
193192
+ ] }) : ""
193193
+ ] });
192607
193194
  if (comps.length > 0) {
192608
- const cx = w / 2;
192609
193195
  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: [
193196
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { className: `dnode-rect ${cls}${composite}`, x: 0, y: 0, width: w, height: h, rx: r }),
193197
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: CONTAINER_TITLE_KIND_BASELINE, textAnchor: "middle", children: [
192612
193198
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192613
193199
  kindShown
192614
193200
  ] }),
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 })
193201
+ nameLine(CONTAINER_TITLE_NAME_BASELINE),
193202
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node, w }),
193203
+ nodeShowsControls(node) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: containerBandBottom(node, NODE_COMPARTMENT_TOP) }) : null
192620
193204
  ] });
192621
193205
  }
192622
193206
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
192623
193207
  /* @__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: [
193208
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: cx, y: h / 2 - 4, textAnchor: "middle", children: [
192629
193209
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
192630
193210
  kindShown
192631
- ] })
193211
+ ] }),
193212
+ nameLine(h / 2 + 11)
192632
193213
  ] });
192633
193214
  }
192634
193215
  function UseCaseBody({ node, w, h }) {
@@ -192809,8 +193390,8 @@ function LifelineBody({ node, w, lineBottom, activation, eventMarks }) {
192809
193390
  ] })
192810
193391
  ] });
192811
193392
  }
192812
- function PortGlyph({ ph, w, h, showLabels, selected: selected2, hovered, moving, connectTarget, connectStart, onSelect, onContext }) {
192813
- const rect = portGlyphRect(ph, w, h);
193393
+ function PortGlyph({ ph, w, h, topReserve = 0, showLabels, selected: selected2, hovered, moving, connectTarget, connectStart, onSelect, onContext }) {
193394
+ const rect = portGlyphRect(ph, w, h, topReserve);
192814
193395
  const x = rect.cx;
192815
193396
  const y = rect.cy;
192816
193397
  const half = rect.across / 2;
@@ -192930,12 +193511,12 @@ function PortGlyph({ ph, w, h, showLabels, selected: selected2, hovered, moving,
192930
193511
  }
192931
193512
  );
192932
193513
  }
192933
- function NestedPortToggles({ ports, w, h, onToggle }) {
193514
+ function NestedPortToggles({ ports, w, h, topReserve = 0, onToggle }) {
192934
193515
  const hosts = ports.filter((ph) => ph.port.meta?.nestedHost === true);
192935
193516
  if (!hosts.length) return null;
192936
193517
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: hosts.map((ph) => {
192937
193518
  const expanded2 = ph.port.meta?.nestedExpanded === true;
192938
- const at = nestedPortTogglePoint(portGlyphRect(ph, w, h), ph.side);
193519
+ const at = nestedPortTogglePoint(portGlyphRect(ph, w, h, topReserve), ph.side);
192939
193520
  const what = ph.port.pin ? "parameters" : "ports";
192940
193521
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
192941
193522
  "button",
@@ -192966,6 +193547,84 @@ function NestedPortToggles({ ports, w, h, onToggle }) {
192966
193547
  );
192967
193548
  }) });
192968
193549
  }
193550
+ function ContainerControlGlyph({ kind, expanded: expanded2 }) {
193551
+ const c = CONTAINER_GLYPH_VIEWBOX / 2;
193552
+ const half = CONTAINER_GLYPH_EXTENT / 2;
193553
+ const box = CONTAINER_GLYPH_EXTENT;
193554
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193555
+ "svg",
193556
+ {
193557
+ className: kind === "internals" ? "internals-glyph" : "feature-list-glyph",
193558
+ viewBox: `0 0 ${CONTAINER_GLYPH_VIEWBOX} ${CONTAINER_GLYPH_VIEWBOX}`,
193559
+ width: CONTAINER_GLYPH_VIEWBOX,
193560
+ height: CONTAINER_GLYPH_VIEWBOX,
193561
+ "aria-hidden": "true",
193562
+ children: kind === "internals" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: [
193563
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c - half, y1: c, x2: c + half, y2: c }),
193564
+ expanded2 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: c, y1: c - half, x2: c, y2: c + half })
193565
+ ] }) : (
193566
+ // A compartment box carrying its rows while the list is HIDDEN and
193567
+ // empty once it is shown (user direction 2026-08-11, correcting the
193568
+ // first reading: "in collapsed state show hamburger menu"). Like
193569
+ // the `+` beside it, the mark advertises what pressing it reveals,
193570
+ // so no colour has to carry the state. The rows are spaced evenly
193571
+ // about the centre line, so the glyph reads as centred either way.
193572
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { stroke: "currentColor", fill: "none", children: [
193573
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: c - half, y: c - half, width: box, height: box, rx: 1.5, strokeWidth: "1.4" }),
193574
+ expanded2 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { strokeWidth: "1.2", strokeLinecap: "round", children: [
193575
+ /* @__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 }),
193576
+ /* @__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 })
193577
+ ] })
193578
+ ] })
193579
+ )
193580
+ }
193581
+ );
193582
+ }
193583
+ function ContainerControls({
193584
+ id: id2,
193585
+ label,
193586
+ internalNoun,
193587
+ internalsVisible,
193588
+ featureCompartmentsVisible,
193589
+ cornerRadius,
193590
+ onToggleInternals,
193591
+ onToggleFeatureCompartments
193592
+ }) {
193593
+ if (!onToggleInternals && !onToggleFeatureCompartments) return null;
193594
+ const inset = containerControlsInset(cornerRadius);
193595
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dcontainer-controls nodrag", style: { right: inset, top: inset }, children: [
193596
+ onToggleFeatureCompartments ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193597
+ "button",
193598
+ {
193599
+ className: "dfeature-toggle nodrag",
193600
+ title: `${featureCompartmentsVisible ? "Hide" : "Show"} feature compartments`,
193601
+ "aria-label": `${featureCompartmentsVisible ? "Hide" : "Show"} feature compartments of ${label}`,
193602
+ "aria-pressed": featureCompartmentsVisible,
193603
+ onPointerDown: (e) => e.stopPropagation(),
193604
+ onClick: (e) => {
193605
+ e.stopPropagation();
193606
+ onToggleFeatureCompartments(id2);
193607
+ },
193608
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerControlGlyph, { kind: "featureList", expanded: featureCompartmentsVisible })
193609
+ }
193610
+ ) : null,
193611
+ onToggleInternals ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193612
+ "button",
193613
+ {
193614
+ className: "div-part-toggle nodrag",
193615
+ title: `${internalsVisible ? "Hide" : "Show"} ${internalNoun}`,
193616
+ "aria-label": `${internalsVisible ? "Hide" : "Show"} ${internalNoun} of ${label}`,
193617
+ "aria-pressed": internalsVisible,
193618
+ onPointerDown: (e) => e.stopPropagation(),
193619
+ onClick: (e) => {
193620
+ e.stopPropagation();
193621
+ onToggleInternals(id2);
193622
+ },
193623
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerControlGlyph, { kind: "internals", expanded: internalsVisible })
193624
+ }
193625
+ ) : null
193626
+ ] });
193627
+ }
192969
193628
  function InlineEditor({ initial, mode, elementKind, onCommit, onCancel, w }) {
192970
193629
  const ref = (0, import_react8.useRef)(null);
192971
193630
  (0, import_react8.useEffect)(() => {
@@ -193011,13 +193670,18 @@ var BAND_HEADER_PX = 26;
193011
193670
  var BAND_MIN_W2 = 200;
193012
193671
  var BAND_MIN_H2 = 80;
193013
193672
  var SMALL_GLYPH_SHAPES = /* @__PURE__ */ new Set(["initial", "final", "terminate", "dot", "fork", "join", "decision", "merge"]);
193673
+ var INTERNAL_NOUN = {
193674
+ iv: "internal parts",
193675
+ afv: "nested actions",
193676
+ stv: "sub-states"
193677
+ };
193014
193678
  function shapeBody(node, data) {
193015
193679
  const { w, h, showMult } = data;
193016
193680
  switch (node.shape) {
193017
193681
  case "state":
193018
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "state" });
193682
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "state", showMult });
193019
193683
  case "action":
193020
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "action" });
193684
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoundedBody, { node, w, h, cls: "action", showMult });
193021
193685
  case "initial":
193022
193686
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InitialBody, { w, h });
193023
193687
  case "final":
@@ -193067,6 +193731,7 @@ function NodeBody({ node, data, selectedPortId, hoveredPortId, movingPortId, con
193067
193731
  ph,
193068
193732
  w,
193069
193733
  h,
193734
+ topReserve: featureBandTopReserve(data),
193070
193735
  showLabels: data.showPortLabels,
193071
193736
  selected: selectedPortId === ph.port.id,
193072
193737
  hovered: hoveredPortId === ph.port.id,
@@ -193096,15 +193761,26 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193096
193761
  const dim = useInteraction(ctx.store, (s) => s.matchedIds ? !s.matchedIds.has(id2) : false);
193097
193762
  const editing = useInteraction(ctx.store, (s) => s.editing?.nodeId === id2 && !s.editing?.at && !s.editing?.edgeId ? s.editing : void 0);
193098
193763
  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";
193764
+ const hiddenContainer = node.meta?.internalsHidden === true;
193765
+ const showsControls = nodeShowsControls(node);
193766
+ const internalNoun = INTERNAL_NOUN[data.kind] ?? "internal parts";
193767
+ const featureCompartmentsVisible = node.meta?.featureCompartmentsVisible === true;
193768
+ const featureCompartmentHeight = featureBandTopReserve(data);
193101
193769
  const containerRef = (0, import_react8.useRef)(null);
193102
193770
  const portSource = (pid) => {
193103
193771
  const p = node.ports?.find((pp) => pp.id === pid);
193104
193772
  ctx.onPortSelected?.();
193105
193773
  ctx.onReveal(p?.source, pid);
193106
193774
  };
193107
- const { dragId, onPortPointerDown } = usePortDrag(containerRef, w, h, ctx.onPortMove, ctx.onPortPreview, portSource);
193775
+ const { dragId, onPortPointerDown } = usePortDrag(
193776
+ containerRef,
193777
+ w,
193778
+ h,
193779
+ featureCompartmentHeight,
193780
+ ctx.onPortMove,
193781
+ ctx.onPortPreview,
193782
+ portSource
193783
+ );
193108
193784
  const ports = data.ports;
193109
193785
  const liveData = { ...data, w, h, ports };
193110
193786
  const directRelation = directRelationForHandle(data.kind, {
@@ -193144,7 +193820,9 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193144
193820
  children: [
193145
193821
  resizable ? (() => {
193146
193822
  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)" });
193823
+ const minWidth = showsControls ? Math.max(min2.minWidth, COLLAPSED_CONTAINER_MIN_WIDTH) : min2.minWidth;
193824
+ const minHeight = Math.max(min2.minHeight, data.featureCompartmentMinHeight ?? 0);
193825
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NodeResizer, { minWidth, minHeight, maxHeight: min2.maxHeight, isVisible: true, color: "var(--accent)" });
193148
193826
  })() : null,
193149
193827
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193150
193828
  NodeBody,
@@ -193160,12 +193838,25 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193160
193838
  onPortContext: ctx.onContextNode
193161
193839
  }
193162
193840
  ),
193841
+ data.kind === "iv" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193842
+ "div",
193843
+ {
193844
+ className: "rf-node-header",
193845
+ title: node.name,
193846
+ style: {
193847
+ left: 0,
193848
+ top: 0,
193849
+ height: NODE_COMPARTMENT_TOP,
193850
+ width: containerTitleArea(w, nodeShowsControls(node)).width
193851
+ }
193852
+ }
193853
+ ) : null,
193163
193854
  SIDES.flatMap((side, si) => {
193164
193855
  const n2 = SIDE_NORMAL[side];
193165
193856
  const out = SMALL_GLYPH_SHAPES.has(node.shape) ? 9 : 0;
193166
193857
  const offsets = sideOffsets[si];
193167
193858
  return offsets.map((offset2, i) => {
193168
- const { x, y } = sideSurfacePoint(node.shape, side, offset2, w, h);
193859
+ const { x, y } = sideSurfacePoint(node.shape, side, offset2, w, h, featureCompartmentHeight);
193169
193860
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193170
193861
  DualHandle,
193171
193862
  {
@@ -193182,7 +193873,7 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193182
193873
  });
193183
193874
  }),
193184
193875
  ports.flatMap((ph) => {
193185
- const rect = portGlyphRect(ph, w, h);
193876
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193186
193877
  const c = { x: rect.cx, y: rect.cy };
193187
193878
  const startable = directRelationForHandle(data.kind, {
193188
193879
  id: ph.port.id,
@@ -193203,7 +193894,7 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193203
193894
  ];
193204
193895
  }),
193205
193896
  ctx.onPortMove ? ports.map((ph) => {
193206
- const rect = portGlyphRect(ph, w, h);
193897
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193207
193898
  const nestedChild = ph.port.parentPort !== void 0;
193208
193899
  const strip = portInteractionStrip(rect, ph.side);
193209
193900
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193237,20 +193928,27 @@ function SysmlNode({ id: id2, data, selected: selected2, width, height }) {
193237
193928
  }
193238
193929
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: handles });
193239
193930
  })() : 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",
193931
+ ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193932
+ NestedPortToggles,
193243
193933
  {
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: "+"
193934
+ ports,
193935
+ w,
193936
+ h,
193937
+ topReserve: featureCompartmentHeight,
193938
+ onToggle: ctx.onToggleNestedPort
193939
+ }
193940
+ ) : null,
193941
+ showsControls ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193942
+ ContainerControls,
193943
+ {
193944
+ id: id2,
193945
+ label: node.name,
193946
+ internalNoun,
193947
+ internalsVisible: !hiddenContainer,
193948
+ featureCompartmentsVisible,
193949
+ cornerRadius: nodeCornerRadius(node.shape, node.isDef, h, (node.compartments?.length ?? 0) > 0),
193950
+ onToggleInternals: ctx.onToggleInternals,
193951
+ onToggleFeatureCompartments: ctx.onToggleFeatureCompartments
193254
193952
  }
193255
193953
  ) : null,
193256
193954
  editingHere && ctx.onEditCommit && ctx.onEditCancel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193286,6 +193984,23 @@ function packageFramePath(w, h) {
193286
193984
  "Z"
193287
193985
  ].join(" ");
193288
193986
  }
193987
+ function frameFeatureBandPath(w, h, bottom, radius) {
193988
+ const left = 1;
193989
+ const right = Math.max(left, w - 1);
193990
+ const top = 1;
193991
+ const end = Math.max(top, Math.min(bottom, h - 1));
193992
+ const r = Math.max(0, Math.min(radius, (right - left) / 2, end - top));
193993
+ if (r === 0) return `M ${left} ${top} H ${right} V ${end} H ${left} Z`;
193994
+ return [
193995
+ `M ${left} ${top + r}`,
193996
+ `Q ${left} ${top} ${left + r} ${top}`,
193997
+ `H ${right - r}`,
193998
+ `Q ${right} ${top} ${right} ${top + r}`,
193999
+ `V ${end}`,
194000
+ `H ${left}`,
194001
+ "Z"
194002
+ ].join(" ");
194003
+ }
193289
194004
  function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193290
194005
  const ctx = useDiagram();
193291
194006
  const frame2 = data.frame;
@@ -193300,13 +194015,30 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193300
194015
  const isPackage2 = frame2.keyword === "package";
193301
194016
  const isLibraryPackage = isPackage2 && frame2.meta?.library === true;
193302
194017
  const resizable = selected2 && (!isPackage2 || data.kind === "gv");
193303
- const collapsible = isCollapsibleFrame(data.kind, frame2) && ctx.onToggleContainer;
194018
+ const collapsible = isCollapsibleFrame(data.kind, frame2);
194019
+ const controllable = collapsible && (ctx.onToggleInternals !== void 0 || ctx.onToggleFeatureCompartments !== void 0);
194020
+ const internalNoun = INTERNAL_NOUN[data.kind] ?? "internal parts";
194021
+ const featureCompartmentsVisible = frame2.meta?.featureCompartmentsVisible === true;
194022
+ const featureBandHeight = data.featureCompartmentHeight ?? frameFeatureCompartmentHeight(frame2);
194023
+ const featureCompartmentHeight = featureBandHeight > 0 ? FRAME_TITLE_HEIGHT + featureBandHeight : 0;
194024
+ const bandBottom = containerBandBottom(
194025
+ featureBandHeight > 0 ? frame2 : { compartments: void 0 },
194026
+ FRAME_TITLE_HEIGHT
194027
+ );
193304
194028
  const containerRef = (0, import_react8.useRef)(null);
193305
194029
  const portSource = (pid) => {
193306
194030
  ctx.onPortSelected?.();
193307
194031
  ctx.onReveal(frame2.ports?.find((p) => p.id === pid)?.source, pid);
193308
194032
  };
193309
- const { dragId, onPortPointerDown } = usePortDrag(containerRef, w, h, ctx.onPortMove, ctx.onPortPreview, portSource);
194033
+ const { dragId, onPortPointerDown } = usePortDrag(
194034
+ containerRef,
194035
+ w,
194036
+ h,
194037
+ featureCompartmentHeight,
194038
+ ctx.onPortMove,
194039
+ ctx.onPortPreview,
194040
+ portSource
194041
+ );
193310
194042
  const ports = data.ports;
193311
194043
  const showPorts = ports.length > 0;
193312
194044
  const isOuterViewFrame = (data.kind === "afv" || data.kind === "stv") && frame2.meta?.anchorFrame === true;
@@ -193345,32 +194077,59 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193345
194077
  ctx.onContextNode(id2, e.clientX, e.clientY);
193346
194078
  },
193347
194079
  children: [
193348
- resizable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NodeResizer, { minWidth: isPackage2 ? 180 : 80, minHeight: isPackage2 ? 100 : 60, isVisible: true, color: "var(--accent)" }) : null,
194080
+ resizable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194081
+ NodeResizer,
194082
+ {
194083
+ minWidth: isPackage2 ? 180 : 80,
194084
+ minHeight: Math.max(isPackage2 ? 100 : 60, data.featureCompartmentMinHeight ?? 0),
194085
+ isVisible: true,
194086
+ color: "var(--accent)"
194087
+ }
194088
+ ) : null,
193349
194089
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { className: "rf-node-svg", width: w, height: h, style: { overflow: "visible" }, children: [
193350
194090
  /* @__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
194091
  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 }),
194092
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194093
+ "path",
194094
+ {
194095
+ className: "dfeature-band-bg",
194096
+ d: frameFeatureBandPath(w, h, bandBottom, frame2.isDef === true ? 0 : 12)
194097
+ }
194098
+ ) : null,
193352
194099
  (() => {
193353
194100
  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);
194101
+ const avail = Math.max(24, containerTitleArea(w, controllable).width - 14);
194102
+ const kindShown = fitKind(kindFull, avail);
194103
+ const mult = data.showMult && frame2.multiplicity ? frame2.multiplicity : void 0;
194104
+ const title = titleNameLine(frame2.label, frame2.type, mult, void 0, avail);
193357
194105
  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: [
194106
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-kind", x: 10, y: CONTAINER_TITLE_KIND_BASELINE, children: [
193359
194107
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: kindFull, shown: kindShown }),
193360
194108
  kindShown
193361
194109
  ] }),
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
194110
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("text", { className: "dnode-label", x: 10, y: CONTAINER_TITLE_NAME_BASELINE, style: { fontSize: 11 }, children: [
194111
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FullTitle, { full: title.full, shown: title.shown }),
194112
+ title.nameShown,
194113
+ title.typeShown ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
194114
+ " : ",
194115
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tspan", { className: "dnode-type", children: title.typeShown })
194116
+ ] }) : "",
194117
+ mult ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tspan", { className: "dnode-kind", children: [
194118
+ " ",
194119
+ mult
194120
+ ] }) : ""
193365
194121
  ] })
193366
194122
  ] });
193367
194123
  })(),
194124
+ featureBandHeight > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Compartments, { node: frame2, w, top: FRAME_TITLE_HEIGHT }) : null,
194125
+ collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContainerBandRule, { w, y: bandBottom }) : null,
193368
194126
  ports.map((ph) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193369
194127
  PortGlyph,
193370
194128
  {
193371
194129
  ph,
193372
194130
  w,
193373
194131
  h,
194132
+ topReserve: featureCompartmentHeight,
193374
194133
  showLabels: true,
193375
194134
  selected: selectedPortId === ph.port.id,
193376
194135
  hovered: hoveredPortId === ph.port.id,
@@ -193383,11 +194142,18 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193383
194142
  ph.port.id
193384
194143
  ))
193385
194144
  ] }),
193386
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "rf-frame-header", style: { left: 0, top: 0, width: "100%", height: 22 }, title: frame2.label }),
194145
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194146
+ "div",
194147
+ {
194148
+ className: "rf-frame-header",
194149
+ style: { left: 0, top: 0, width: "100%", height: FRAME_TITLE_HEIGHT },
194150
+ title: frame2.label
194151
+ }
194152
+ ),
193387
194153
  SIDES.flatMap((side, si) => {
193388
194154
  const offsets = sideOffsets[si];
193389
194155
  return offsets.map((offset2, i) => {
193390
- const { x, y } = sideXY(side, offset2, w, h);
194156
+ const { x, y } = sidePoint(side, offset2, w, h, featureCompartmentHeight);
193391
194157
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193392
194158
  DualHandle,
193393
194159
  {
@@ -193404,7 +194170,7 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193404
194170
  });
193405
194171
  }),
193406
194172
  showPorts ? ports.flatMap((ph) => {
193407
- const rect = portGlyphRect(ph, w, h);
194173
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193408
194174
  const c = { x: rect.cx, y: rect.cy };
193409
194175
  const startable = directRelationForHandle(data.kind, {
193410
194176
  id: ph.port.id,
@@ -193425,7 +194191,7 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193425
194191
  ];
193426
194192
  }) : null,
193427
194193
  ctx.onPortMove ? ports.map((ph) => {
193428
- const rect = portGlyphRect(ph, w, h);
194194
+ const rect = portGlyphRect(ph, w, h, featureCompartmentHeight);
193429
194195
  const nestedChild = ph.port.parentPort !== void 0;
193430
194196
  const strip = portInteractionStrip(rect, ph.side);
193431
194197
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193447,20 +194213,27 @@ function FrameNode({ id: id2, data, selected: selected2, width, height }) {
193447
194213
  `pd-${ph.port.id}`
193448
194214
  );
193449
194215
  }) : null,
193450
- ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NestedPortToggles, { ports, w, h, onToggle: ctx.onToggleNestedPort }) : null,
194216
+ ctx.onToggleNestedPort ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
194217
+ NestedPortToggles,
194218
+ {
194219
+ ports,
194220
+ w,
194221
+ h,
194222
+ topReserve: featureCompartmentHeight,
194223
+ onToggle: ctx.onToggleNestedPort
194224
+ }
194225
+ ) : null,
193451
194226
  collapsible ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
193452
- "button",
194227
+ ContainerControls,
193453
194228
  {
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"
194229
+ id: id2,
194230
+ label: frame2.label,
194231
+ internalNoun,
194232
+ internalsVisible: true,
194233
+ featureCompartmentsVisible,
194234
+ cornerRadius: frame2.isDef === true ? 0 : FRAME_USAGE_CORNER_RADIUS,
194235
+ onToggleInternals: ctx.onToggleInternals,
194236
+ onToggleFeatureCompartments: ctx.onToggleFeatureCompartments
193464
194237
  }
193465
194238
  ) : null,
193466
194239
  editing && ctx.onEditCommit && ctx.onEditCancel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -193648,9 +194421,9 @@ function resolveViewConfig(settings, view, overrides = {}) {
193648
194421
  gvFilters: new Set(view?.gvFilters ?? []),
193649
194422
  // Case View definitions are shown unless the user turned them off.
193650
194423
  cvShowDefs: view?.cvShowDefs !== false,
193651
- ivCompartments: view?.ivCompartments ?? {},
194424
+ hiddenInternals: view?.hiddenInternals ?? {},
194425
+ visibleFeatureCompartments: view?.visibleFeatureCompartments ?? {},
193652
194426
  nestedPorts: view?.nestedPorts ?? {},
193653
- collapsedActions: view?.collapsedActions ?? {},
193654
194427
  showPortLabels: settings.showPortLabels,
193655
194428
  showMultiplicities: settings.showMultiplicities,
193656
194429
  showSubjectAsBoundaryBox: settings.showSubjectAsBoundaryBox,
@@ -193725,9 +194498,9 @@ async function renderDiagramSvg(document2, provider, options) {
193725
194498
  showMult: config.showMultiplicities,
193726
194499
  connectPointSpacing: config.connectPointSpacing,
193727
194500
  subjectBoundary: config.showSubjectAsBoundaryBox,
193728
- ivCompartments: config.ivCompartments,
194501
+ hiddenInternals: config.hiddenInternals,
194502
+ visibleFeatureCompartments: config.visibleFeatureCompartments,
193729
194503
  nestedPorts: config.nestedPorts,
193730
- collapsedActions: config.collapsedActions,
193731
194504
  overrides: config.overrides,
193732
194505
  portOverrides: config.portOverrides,
193733
194506
  edgeRoutes: config.edgeRoutes,
@@ -193797,7 +194570,7 @@ async function runExport(command) {
193797
194570
  }
193798
194571
 
193799
194572
  // src/main.ts
193800
- var VERSION2 = true ? "0.14.5" : "dev";
194573
+ var VERSION2 = true ? "0.15.1" : "dev";
193801
194574
  function display(file) {
193802
194575
  const rel2 = path9.relative(process.cwd(), file);
193803
194576
  return rel2 && !rel2.startsWith("..") ? rel2.split(path9.sep).join("/") : file;