@buun_group/plotwork-mcp 0.1.0-beta.7 → 0.1.0-beta.8

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/plotwork-mcp.mjs CHANGED
@@ -51,7 +51,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
51
51
  var package_default;
52
52
  var init_package = __esm({
53
53
  "package.json"() {
54
- package_default = { version: "0.1.0-beta.7" };
54
+ package_default = { version: "0.1.0-beta.8" };
55
55
  }
56
56
  });
57
57
 
@@ -160,6 +160,71 @@ var init_flow_node_types = __esm({
160
160
  }
161
161
  });
162
162
 
163
+ // ../../packages/diagrams/src/constants/auto-group-types.ts
164
+ var AUTO_GROUP_TYPES, REGION_CODE, AZURE_REGION_NAMES;
165
+ var init_auto_group_types = __esm({
166
+ "../../packages/diagrams/src/constants/auto-group-types.ts"() {
167
+ "use strict";
168
+ AUTO_GROUP_TYPES = {
169
+ network: { ends: ["vpc", "vnet", "network", "virtual network", "private network", "virtual private cloud", "vcn"] },
170
+ subnet: { ends: ["subnet", "subnets"] },
171
+ "public-subnet": { ends: ["public subnet", "public subnets"] },
172
+ "private-subnet": { ends: ["private subnet", "private subnets"] },
173
+ environment: { ends: ["environment", "env"], whole: ["production", "prod", "staging", "dev", "development", "qa", "uat"] },
174
+ "ci-cd": {
175
+ ends: ["ci", "ci cd", "cicd", "ci pipeline", "ci cd pipeline", "build pipeline", "release pipeline", "deploy pipeline", "deployment pipeline", "delivery pipeline"],
176
+ whole: ["pipeline", "pipelines"]
177
+ },
178
+ messaging: { ends: ["kafka", "kafka cluster", "queue", "queues", "message queue", "message queues", "event bus", "message bus", "messaging", "event streaming", "service bus", "pub sub", "pubsub"] },
179
+ cluster: { ends: ["kubernetes", "k8s", "kubernetes cluster", "k8s cluster", "eks cluster", "aks cluster", "gke cluster", "container cluster"], whole: ["cluster"] },
180
+ region: { ends: ["region"] },
181
+ account: { ends: ["aws account"] },
182
+ "data-tier": { ends: ["data", "data tier", "database tier", "data layer", "database layer", "databases", "data stores", "data store", "storage tier", "persistence", "persistence layer"] },
183
+ security: { ends: ["security", "iam", "security services"] },
184
+ observability: { ends: ["monitoring", "observability", "logging", "telemetry"] }
185
+ };
186
+ REGION_CODE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z]+-\d|(?:us|europe|asia|australia|southamerica|northamerica|me|africa)-[a-z]+\d)$/;
187
+ AZURE_REGION_NAMES = /* @__PURE__ */ new Set([
188
+ "eastus",
189
+ "eastus2",
190
+ "westus",
191
+ "westus2",
192
+ "westus3",
193
+ "centralus",
194
+ "northcentralus",
195
+ "southcentralus",
196
+ "westcentralus",
197
+ "canadacentral",
198
+ "canadaeast",
199
+ "brazilsouth",
200
+ "northeurope",
201
+ "westeurope",
202
+ "uksouth",
203
+ "ukwest",
204
+ "francecentral",
205
+ "germanywestcentral",
206
+ "swedencentral",
207
+ "switzerlandnorth",
208
+ "norwayeast",
209
+ "polandcentral",
210
+ "italynorth",
211
+ "eastasia",
212
+ "southeastasia",
213
+ "japaneast",
214
+ "japanwest",
215
+ "koreacentral",
216
+ "centralindia",
217
+ "southindia",
218
+ "australiaeast",
219
+ "australiasoutheast",
220
+ "uaenorth",
221
+ "southafricanorth",
222
+ "qatarcentral",
223
+ "israelcentral"
224
+ ]);
225
+ }
226
+ });
227
+
163
228
  // ../../packages/diagrams/src/constants/group-types.ts
164
229
  function groupTypeIcon(type, provider, canUse = () => true) {
165
230
  const spec = GROUP_TYPE_SPECS[type];
@@ -386,7 +451,7 @@ var init_auto_icons = __esm({
386
451
  ICON_MODE_SPEC = {
387
452
  key: "icons",
388
453
  values: ICON_MODES.join(", "),
389
- meaning: 'auto (the default): an item with no icon= gets the icon its label, else its sub= or tech=, names for certain: a brand ("Postgres"), a cloud product ("Amazon S3"), or in some kinds a role ("Email"). A name that is also an everyday word ("Vault") stays plain. icon=none keeps one item plain; off keeps all plain.',
454
+ meaning: 'auto (the default): an item with no icon= gets the icon its label, else its sub= or tech=, names for certain: a brand ("Postgres"), a cloud product ("Amazon S3"), or in some kinds a role ("Email"). A name that is also an everyday word ("Vault") stays plain. icon=none keeps one item plain; off keeps all plain, with flow edge labels, and stops a flow group taking its type from its name.',
390
455
  example: 'flow "Checkout" icons=off'
391
456
  };
392
457
  PLAIN_WORD_NAMES = /* @__PURE__ */ new Set([
@@ -643,7 +708,7 @@ var init_view_links = __esm({
643
708
  };
644
709
  CLI_RELEASE = {
645
710
  packageName: "@buun_group/plotwork",
646
- version: "0.1.0-beta.8",
711
+ version: "0.1.0-beta.9",
647
712
  onNpm: true
648
713
  };
649
714
  }
@@ -3500,6 +3565,195 @@ var init_label_icons = __esm({
3500
3565
  }
3501
3566
  });
3502
3567
 
3568
+ // ../../packages/diagrams/src/constants/label-vocabulary.ts
3569
+ var EDGE_STYLES, LABEL_VERB_PHRASES, LABEL_VERB_MAX_WORDS, NOT_VERB_PHRASES, STYLE_STEPS;
3570
+ var init_label_vocabulary = __esm({
3571
+ "../../packages/diagrams/src/constants/label-vocabulary.ts"() {
3572
+ "use strict";
3573
+ EDGE_STYLES = {
3574
+ /** Reads stored data: a query, a select. */
3575
+ reads: { icon: "lucide:eye", role: "data" },
3576
+ /** Reads its settings, such as a Kubernetes ConfigMap; not stored data, so no role. */
3577
+ "reads-settings": { icon: "lucide:eye", role: null },
3578
+ /** Writes stored data: an insert, an update. */
3579
+ writes: { icon: "lucide:square-pen", role: "data" },
3580
+ /** Reads and writes a store the file names without saying which: a database, a bucket, a cache. */
3581
+ stores: { icon: "lucide:database", role: "data" },
3582
+ /** Mounts a volume or a claim. */
3583
+ mounts: { icon: "lucide:hard-drive", role: "data" },
3584
+ /** Sends, publishes, produces, or emits a message it does not wait for. */
3585
+ sends: { icon: "lucide:send", role: "async" },
3586
+ /** Receives, consumes, or subscribes to messages. */
3587
+ receives: { icon: "lucide:inbox", role: "async" },
3588
+ /** Calls or requests and waits for the answer. */
3589
+ calls: { icon: "lucide:arrow-right-left", role: null },
3590
+ /** Routes outside traffic: a host, a path, a public address, a DNS name. */
3591
+ routes: { icon: "lucide:globe", role: null },
3592
+ /** Needs another thing to be there first. */
3593
+ "depends-on": { icon: "lucide:link", role: null },
3594
+ /** Provides an API. */
3595
+ provides: { icon: "lucide:plug", role: null },
3596
+ /** Consumes an API. */
3597
+ consumes: { icon: "lucide:plug-zap", role: null },
3598
+ /** Starts another thing: a function on an event, a downstream pipeline. */
3599
+ triggers: { icon: "lucide:zap", role: null },
3600
+ /** Deploys to an environment. */
3601
+ deploys: { icon: "lucide:rocket", role: null },
3602
+ /** Grants a role, assumes one, or authenticates. */
3603
+ grants: { icon: "lucide:key-round", role: "identity" },
3604
+ /** Uses a credential: a secret, a password, a key. */
3605
+ secret: { icon: "lucide:key-round", role: "identity" },
3606
+ /** Reaches another over a private network: a private endpoint, VPC peering, VNet integration. */
3607
+ private: { icon: "lucide:lock", role: "private" },
3608
+ /** Is guarded by a firewall or a security group. */
3609
+ filters: { icon: "lucide:shield", role: null },
3610
+ /** Sends logs or telemetry. */
3611
+ reports: { icon: "lucide:activity", role: null },
3612
+ /** Pulls container images. */
3613
+ pulls: { icon: "lucide:download", role: null },
3614
+ /** Builds on a base, such as a Kustomize overlay on its base. */
3615
+ "builds-on": { icon: "lucide:layers", role: null },
3616
+ /** Includes a component. */
3617
+ includes: { icon: "lucide:package", role: null },
3618
+ /** Patches what another file holds. */
3619
+ patches: { icon: "lucide:replace", role: null }
3620
+ };
3621
+ LABEL_VERB_PHRASES = {
3622
+ reads: ["reads", "read", "reading", "reads from", "read from", "queries", "query", "querying", "looks up", "look up", "selects", "select from", "loads from", "fetches from"],
3623
+ writes: [
3624
+ "writes",
3625
+ "write",
3626
+ "writing",
3627
+ "writes to",
3628
+ "write to",
3629
+ "inserts",
3630
+ "insert",
3631
+ "inserts into",
3632
+ "updates",
3633
+ "update",
3634
+ "saves",
3635
+ "save",
3636
+ "saves to",
3637
+ "persists",
3638
+ "persist",
3639
+ "persists to",
3640
+ "deletes",
3641
+ "delete",
3642
+ "deletes from",
3643
+ "uploads",
3644
+ "upload",
3645
+ "uploads to",
3646
+ "upload to"
3647
+ ],
3648
+ stores: ["stores", "store", "stores in", "store in", "caches", "caches in", "reads and writes", "reads writes", "read write", "read and write"],
3649
+ mounts: ["mounts", "mount"],
3650
+ sends: [
3651
+ "sends",
3652
+ "send",
3653
+ "sends to",
3654
+ "publishes",
3655
+ "publish",
3656
+ "publishes to",
3657
+ "publish to",
3658
+ "emits",
3659
+ "emit",
3660
+ "produces",
3661
+ "produce",
3662
+ "produces to",
3663
+ "enqueues",
3664
+ "enqueue",
3665
+ "pushes to",
3666
+ "notifies",
3667
+ "notify",
3668
+ "fires"
3669
+ ],
3670
+ receives: ["receives", "receive", "consumes", "consume", "consumes from", "subscribes", "subscribes to", "subscribe to", "listens to", "listens on", "polls", "dequeues"],
3671
+ calls: ["calls", "call", "invokes", "invoke", "requests", "request", "fetches", "fetch", "queries api", "http", "https", "grpc", "rest", "graphql"],
3672
+ routes: ["routes", "routes to", "route to", "forwards", "forwards to", "forward to", "proxies", "proxies to", "redirects to", "load balances"],
3673
+ "depends-on": ["depends on", "depend on", "requires", "needs"],
3674
+ provides: ["provides", "exposes", "serves"],
3675
+ triggers: ["triggers", "trigger", "starts", "kicks off", "schedules", "wakes"],
3676
+ deploys: ["deploys", "deploy", "deploys to", "deploy to", "releases to", "ships to", "promotes to", "rolls out", "rolls out to"],
3677
+ grants: [
3678
+ "authenticates",
3679
+ "authenticate",
3680
+ "authenticates with",
3681
+ "authenticates against",
3682
+ "authorizes",
3683
+ "authorize",
3684
+ "authorises",
3685
+ "authorise",
3686
+ "signs in",
3687
+ "sign in",
3688
+ "signs in with",
3689
+ "logs in",
3690
+ "log in",
3691
+ "logs in with",
3692
+ "grants",
3693
+ "grant",
3694
+ "assumes role",
3695
+ "issues token",
3696
+ "issues tokens",
3697
+ "verifies token",
3698
+ "verify token",
3699
+ "validates token",
3700
+ "validate token",
3701
+ "logs out",
3702
+ "log out",
3703
+ "signs out",
3704
+ "sign out"
3705
+ ],
3706
+ secret: ["reads secret", "reads secrets", "read secret", "read secrets", "read credentials", "fetches secret", "fetches secrets", "reads credentials", "gets secret", "gets secrets"],
3707
+ reports: [
3708
+ "reports",
3709
+ "reports to",
3710
+ "logs",
3711
+ "logs to",
3712
+ "metrics",
3713
+ "telemetry",
3714
+ "traces",
3715
+ "logs and metrics",
3716
+ "sends logs",
3717
+ "sends logs to",
3718
+ "sends metrics",
3719
+ "sends metrics to",
3720
+ "sends telemetry",
3721
+ "sends traces",
3722
+ "ships logs",
3723
+ "ships logs to",
3724
+ "exports metrics",
3725
+ "exports traces",
3726
+ "emits metrics",
3727
+ "emits logs"
3728
+ ],
3729
+ pulls: ["pulls image", "pulls images", "pulls images from", "pulls image from"]
3730
+ };
3731
+ LABEL_VERB_MAX_WORDS = 5;
3732
+ NOT_VERB_PHRASES = [
3733
+ "read replica",
3734
+ "read replicas",
3735
+ "read model",
3736
+ "read only",
3737
+ "write model",
3738
+ "write ahead",
3739
+ "request id",
3740
+ "request body",
3741
+ "call center",
3742
+ "call centre",
3743
+ "update available",
3744
+ "query string",
3745
+ "query params"
3746
+ ];
3747
+ STYLE_STEPS = [
3748
+ 'Label each arrow with a verb, such as "reads", "writes orders", "publishes OrderPlaced", "calls", "deploys to", or "authenticates": the label then draws the icon of that relationship (verbIcons). icon= picks another icon; icon=none keeps the label plain.',
3749
+ "Add role= to the arrows whose line means something: role=data for reads and writes of stored data, role=async for messages nobody waits for, role=identity for sign-in and credentials, role=private for private network links. The line takes the role's colour and the legend names it; warning W500 lists the arrows to mark.",
3750
+ 'Give every group a type= (network, subnet, environment, data-tier, messaging, cluster, ci-cd, observability, and the rest), so its frame, tone, and icon follow. A group named plainly, such as "VPC", "Production", "Kafka", or "Monitoring", gets its type by itself; type=none keeps it plain.',
3751
+ 'Name each node after the product it is ("Postgres", "Redis", "Amazon S3") so it draws the mark, and choose the node type that says what it is.',
3752
+ "Keep labels to two to four words; the picture carries the rest."
3753
+ ];
3754
+ }
3755
+ });
3756
+
3503
3757
  // ../../packages/diagrams/src/constants/status-icons.ts
3504
3758
  var STATUS_ICON_SPECS;
3505
3759
  var init_status_icons = __esm({
@@ -3548,6 +3802,7 @@ var init_constants = __esm({
3548
3802
  init_directions();
3549
3803
  init_word_list();
3550
3804
  init_flow_node_types();
3805
+ init_auto_group_types();
3551
3806
  init_group_types();
3552
3807
  init_overlays();
3553
3808
  init_node_change();
@@ -3576,6 +3831,7 @@ var init_constants = __esm({
3576
3831
  init_icon_concepts();
3577
3832
  init_icon_sets();
3578
3833
  init_label_icons();
3834
+ init_label_vocabulary();
3579
3835
  init_status_icons();
3580
3836
  init_tones();
3581
3837
  }
@@ -3644,7 +3900,7 @@ function formatDiagnostic(diagnostic, source) {
3644
3900
  if (diagnostic.hint !== null) parts.push(`hint: ${diagnostic.hint}`);
3645
3901
  return parts.join("\n");
3646
3902
  }
3647
- var DIAGNOSTIC_CODES;
3903
+ var DIAGNOSTIC_CODES, STYLE_HINT_CODES;
3648
3904
  var init_diagnostics = __esm({
3649
3905
  "../../packages/diagrams/src/core/diagnostics.ts"() {
3650
3906
  "use strict";
@@ -3677,6 +3933,9 @@ var init_diagnostics = __esm({
3677
3933
  suggestIcon: "W204",
3678
3934
  suggestGroupType: "W205",
3679
3935
  overlayCovers: "W206",
3936
+ // Style suggestions. W5xx says how a valid diagram would look more finished; like W204 and W205 it is a suggestion, not a problem
3937
+ // (`isStyleHint`), so a check that fails on warnings passes it.
3938
+ suggestLineRole: "W500",
3680
3939
  // Mermaid import. M1xx stops a conversion; M2xx reports something dropped or changed and never stops it.
3681
3940
  // They point into the Mermaid source that was submitted, not into the Plotwork source.
3682
3941
  mermaidNotDiagram: "M100",
@@ -3853,6 +4112,7 @@ var init_diagnostics = __esm({
3853
4112
  overrideRefused: "S202",
3854
4113
  overridesMissing: "S203"
3855
4114
  };
4115
+ STYLE_HINT_CODES = /* @__PURE__ */ new Set([DIAGNOSTIC_CODES.suggestIcon, DIAGNOSTIC_CODES.suggestGroupType, DIAGNOSTIC_CODES.suggestLineRole]);
3856
4116
  }
3857
4117
  });
3858
4118
 
@@ -4023,13 +4283,13 @@ function indexOf(set2) {
4023
4283
  const known3 = indexes.get(set2);
4024
4284
  if (known3 !== void 0) return known3;
4025
4285
  const byName = /* @__PURE__ */ new Map();
4026
- const phrases = /* @__PURE__ */ new Map();
4286
+ const phrases2 = /* @__PURE__ */ new Map();
4027
4287
  for (const icon of set2.icons) {
4028
4288
  if (byName.has(icon.name)) continue;
4029
4289
  byName.set(icon.name, definitionOf(icon));
4030
- phrases.set(icon.name, new Set([normalizePhrase(icon.name), ...icon.aliases.map(normalizePhrase)].filter(Boolean)));
4290
+ phrases2.set(icon.name, new Set([normalizePhrase(icon.name), ...icon.aliases.map(normalizePhrase)].filter(Boolean)));
4031
4291
  }
4032
- const index2 = { byName, phrases };
4292
+ const index2 = { byName, phrases: phrases2 };
4033
4293
  indexes.set(set2, index2);
4034
4294
  return index2;
4035
4295
  }
@@ -6697,9 +6957,9 @@ function cloudPhrases(generic) {
6697
6957
  const ref = `${set2}:${spec.name}`;
6698
6958
  const bases = [normalizePhrase(spec.name), ...(spec.aliases ?? []).map(normalizePhrase)];
6699
6959
  const title = normalizePhrase(spec.title);
6700
- const phrases = /* @__PURE__ */ new Set([title, ...bases]);
6701
- for (const providerName2 of providerNames) for (const base of bases) phrases.add(`${providerName2} ${base}`);
6702
- for (const phrase2 of phrases) {
6960
+ const phrases2 = /* @__PURE__ */ new Set([title, ...bases]);
6961
+ for (const providerName2 of providerNames) for (const base of bases) phrases2.add(`${providerName2} ${base}`);
6962
+ for (const phrase2 of phrases2) {
6703
6963
  if (spec.vendor !== void 0 && claimed.has(phrase2)) continue;
6704
6964
  const isScoped = spec.scopeToProvider === true && phrase2 !== title && !providerNames.some((providerName2) => phrase2.startsWith(`${providerName2} `));
6705
6965
  out.push({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: set2, isGeneric: generic.has(phrase2) || isScoped, isBrand: false });
@@ -6712,8 +6972,8 @@ function cloudPhrases(generic) {
6712
6972
  function brandPhrases() {
6713
6973
  return listSetNames("simple").flatMap((name2) => {
6714
6974
  const ref = `simple:${name2}`;
6715
- const phrases = /* @__PURE__ */ new Set([normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase), ...(findIcon(ref)?.aliases ?? []).map(normalizePhrase)]);
6716
- return [...phrases].map((phrase2) => ({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: "simple", isGeneric: false, isBrand: true }));
6975
+ const phrases2 = /* @__PURE__ */ new Set([normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase), ...(findIcon(ref)?.aliases ?? []).map(normalizePhrase)]);
6976
+ return [...phrases2].map((phrase2) => ({ phrase: phrase2, words: phrase2.split(" ").length, ref, set: "simple", isGeneric: false, isBrand: true }));
6717
6977
  });
6718
6978
  }
6719
6979
  function listProductPhrases() {
@@ -6800,7 +7060,7 @@ function indexIcon(ref) {
6800
7060
  const glyph = glyphNameOf(ref);
6801
7061
  const plainNames = [normalizePhrase(name2), ...(ICON_ALIASES[ref] ?? []).map(normalizePhrase)];
6802
7062
  const aliases = (icon?.aliases ?? []).map(normalizePhrase);
6803
- const phrases = /* @__PURE__ */ new Set([...plainNames, ...aliases]);
7063
+ const phrases2 = /* @__PURE__ */ new Set([...plainNames, ...aliases]);
6804
7064
  const kindPhrases = /* @__PURE__ */ new Set();
6805
7065
  const providerKindPhrases = /* @__PURE__ */ new Set();
6806
7066
  const titleWords = /* @__PURE__ */ new Set();
@@ -6809,11 +7069,11 @@ function indexIcon(ref) {
6809
7069
  for (const word2 of normalizePhrase(spec.title).split(" ")) titleWords.add(word2);
6810
7070
  for (const base of [...plainNames, ...aliases, normalizePhrase(spec.title)]) stems.add(stemPhrase(base));
6811
7071
  const provider = CLOUD_PROVIDER_SPECS[cloudSet];
6812
- phrases.add(normalizePhrase(spec.title));
6813
- if (spec.kind === "provider") for (const providerName2 of provider.names) phrases.add(providerName2);
7072
+ phrases2.add(normalizePhrase(spec.title));
7073
+ if (spec.kind === "provider") for (const providerName2 of provider.names) phrases2.add(providerName2);
6814
7074
  const kindPhraseList = CLOUD_KIND_SPECS[spec.kind].phrases.map(normalizePhrase);
6815
7075
  for (const providerName2 of provider.names) {
6816
- for (const base of [...plainNames, ...aliases]) phrases.add(`${providerName2} ${base}`);
7076
+ for (const base of [...plainNames, ...aliases]) phrases2.add(`${providerName2} ${base}`);
6817
7077
  for (const kindPhrase of kindPhraseList) providerKindPhrases.add(`${providerName2} ${kindPhrase}`);
6818
7078
  }
6819
7079
  for (const kindPhrase of kindPhraseList) kindPhrases.add(kindPhrase);
@@ -6822,8 +7082,8 @@ function indexIcon(ref) {
6822
7082
  ref,
6823
7083
  tags: (icon?.tags ?? []).map((tag) => tag.toLowerCase()),
6824
7084
  concept: glyph === null ? void 0 : CONCEPTS_BY_GLYPH.get(glyph),
6825
- phrases,
6826
- haystack: ` ${[...phrases].join(" | ")} `,
7085
+ phrases: phrases2,
7086
+ haystack: ` ${[...phrases2].join(" | ")} `,
6827
7087
  stems,
6828
7088
  titleWords,
6829
7089
  isVendorOnly: spec?.vendor !== void 0,
@@ -110258,9 +110518,39 @@ var init_flow_outline = __esm({
110258
110518
  }
110259
110519
  });
110260
110520
 
110521
+ // ../../packages/diagrams/src/parse/label-icon.ts
110522
+ function verbPhrases() {
110523
+ phrases ??= Object.entries(LABEL_VERB_PHRASES).flatMap(([verb, words2]) => (words2 ?? []).map((phrase2) => [phrase2, verb])).sort((a, b) => b[0].split(" ").length - a[0].split(" ").length || b[0].length - a[0].length);
110524
+ return phrases;
110525
+ }
110526
+ function startsWithWords(text3, phrase2) {
110527
+ return text3 === phrase2 || text3.startsWith(`${phrase2} `);
110528
+ }
110529
+ function verbNamedBy(label) {
110530
+ const text3 = normalizePhrase(label);
110531
+ if (text3 === "" || text3.split(" ").length > LABEL_VERB_MAX_WORDS) return null;
110532
+ if (NOT_VERB_PHRASES.some((phrase2) => startsWithWords(text3, phrase2))) return null;
110533
+ const found = verbPhrases().find(([phrase2]) => startsWithWords(text3, phrase2));
110534
+ return found === void 0 ? null : found[1];
110535
+ }
110536
+ function labelIconFor(label) {
110537
+ const word2 = label.trim().toLowerCase();
110538
+ if (Object.hasOwn(LABEL_ICON_DEFAULTS, word2)) return LABEL_ICON_DEFAULTS[word2] ?? null;
110539
+ const verb = verbNamedBy(label);
110540
+ return verb === null ? null : EDGE_STYLES[verb].icon;
110541
+ }
110542
+ var phrases;
110543
+ var init_label_icon = __esm({
110544
+ "../../packages/diagrams/src/parse/label-icon.ts"() {
110545
+ "use strict";
110546
+ init_constants();
110547
+ init_phrase();
110548
+ }
110549
+ });
110550
+
110261
110551
  // ../../packages/diagrams/src/parse/group-options.ts
110262
110552
  function resolveGroupType(option, state) {
110263
- if (option === void 0) return null;
110553
+ if (option === void 0 || option.value === PLAIN_GROUP_TYPE) return null;
110264
110554
  const type = GROUP_TYPES.find((candidate2) => candidate2 === option.value);
110265
110555
  if (type !== void 0) return type;
110266
110556
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownGroupType, `Unknown group type "${option.value}"`, option.valueToken, didYouMean(option.value, GROUP_TYPES)));
@@ -110273,8 +110563,29 @@ function resolveProvider(option, state) {
110273
110563
  state.report.push(errorAt(DIAGNOSTIC_CODES.unknownProvider, `Unknown provider "${option.value}"`, option.valueToken, providerHint(option.value)));
110274
110564
  return null;
110275
110565
  }
110276
- function resolveGroupLook(options3, parent, state) {
110277
- const type = resolveGroupType(findOption(options3, "type"), state);
110566
+ function wordsOf2(phrase2) {
110567
+ return phrase2.split(" ").length;
110568
+ }
110569
+ function longestOtherReading(normalized, type) {
110570
+ const lengths = GROUP_TYPES.filter((other) => other !== type).flatMap((other) => GROUP_TYPE_SPECS[other].phrases.filter((phrase2) => ` ${normalized} `.includes(` ${phrase2} `)).map(wordsOf2));
110571
+ return Math.max(0, ...lengths);
110572
+ }
110573
+ function autoGroupType(label) {
110574
+ const trimmed4 = label.trim().toLowerCase();
110575
+ if (REGION_CODE.test(trimmed4) || AZURE_REGION_NAMES.has(trimmed4)) return "region";
110576
+ const normalized = normalizePhrase(label);
110577
+ let best = null;
110578
+ for (const [type, phrases2] of Object.entries(AUTO_GROUP_TYPES)) {
110579
+ const fits = [...(phrases2?.ends ?? []).filter((phrase2) => normalized === phrase2 || normalized.endsWith(` ${phrase2}`)), ...(phrases2?.whole ?? []).filter((phrase2) => normalized === phrase2)];
110580
+ for (const phrase2 of fits) if (best === null || wordsOf2(phrase2) > best.words) best = { type, words: wordsOf2(phrase2) };
110581
+ }
110582
+ return best === null || longestOtherReading(normalized, best.type) > best.words ? null : best.type;
110583
+ }
110584
+ function resolveGroupLook(options3, parent, state, label = null) {
110585
+ const typeOption = findOption(options3, "type");
110586
+ const isPlain = typeOption?.value === PLAIN_GROUP_TYPE;
110587
+ const named = typeOption === void 0 && label !== null && !iconsAreOff(state) ? autoGroupType(label) : null;
110588
+ const type = named ?? resolveGroupType(typeOption, state);
110278
110589
  const inherited = parent === null ? state.provider : state.groups.find((group2) => group2.id === parent)?.provider ?? state.provider;
110279
110590
  const provider = resolveProvider(findOption(options3, "provider"), state) ?? inherited;
110280
110591
  const spec = type === null ? null : GROUP_TYPE_SPECS[type];
@@ -110283,19 +110594,24 @@ function resolveGroupLook(options3, parent, state) {
110283
110594
  provider,
110284
110595
  tone: resolveTone(findOption(options3, "tone"), spec?.tone ?? GROUP_DEFAULTS.tone, state),
110285
110596
  icon: resolveIcon(findOption(options3, "icon"), type === null ? GROUP_DEFAULTS.icon : groupIcon(type, provider), state),
110286
- frame: spec?.frame ?? GROUP_DEFAULTS.frame
110597
+ frame: spec?.frame ?? GROUP_DEFAULTS.frame,
110598
+ ...isPlain ? { isPlain: true } : {}
110287
110599
  };
110288
110600
  }
110601
+ var PLAIN_GROUP_TYPE;
110289
110602
  var init_group_options = __esm({
110290
110603
  "../../packages/diagrams/src/parse/group-options.ts"() {
110291
110604
  "use strict";
110292
110605
  init_constants();
110293
110606
  init_core();
110294
110607
  init_icons();
110608
+ init_phrase();
110609
+ init_auto_icon();
110295
110610
  init_icon_hint();
110296
110611
  init_options();
110297
110612
  init_statement_helpers();
110298
110613
  init_suggest();
110614
+ PLAIN_GROUP_TYPE = "none";
110299
110615
  }
110300
110616
  });
110301
110617
 
@@ -110538,7 +110854,7 @@ function parseGroupStatement(cursor, state, line3, parent) {
110538
110854
  state.groups.push({
110539
110855
  id: groupId,
110540
110856
  label,
110541
- ...resolveGroupLook(options3, parent, state),
110857
+ ...resolveGroupLook(options3, parent, state, label),
110542
110858
  parent,
110543
110859
  sub: findOption(options3, "sub")?.value ?? null,
110544
110860
  stack: resolveStack(findOption(options3, "stack"), state, true),
@@ -110565,12 +110881,12 @@ function parseEdgeStatement(cursor, state, line3) {
110565
110881
  if (iconOption2 !== void 0 && label === null) {
110566
110882
  state.report.push(errorAt(DIAGNOSTIC_CODES.syntax, "An edge icon needs a label", iconOption2.keyToken, 'Write: a -> b : "label" icon=status:success'));
110567
110883
  }
110568
- const icon = resolveIcon(iconOption2, label === null ? null : LABEL_ICON_DEFAULTS[label.trim().toLowerCase()] ?? null, state);
110884
+ const icon = resolveIcon(iconOption2, label === null || iconsAreOff(state) ? null : labelIconFor(label), state);
110569
110885
  rejectTrailing(cursor, state, "Write one edge per line; chains like a -> b -> c are not supported.");
110570
110886
  const spec = ARROW_SPECS[arrow.text];
110571
110887
  const motion = readLineMotion(options3, state.motion, spec.heads, from, state.report);
110572
110888
  const role = readLineRole(findOption(options3, "role"), state.report);
110573
- state.edges.push({ from: from.text, to: to.text, fromToken: from, toToken: to, label, icon, line: spec.line, heads: spec.heads, ...motion, ...role === void 0 ? {} : { role } });
110889
+ state.edges.push({ from: from.text, to: to.text, fromToken: from, toToken: to, label, icon, ...iconOption2 === void 0 ? {} : { hasOwnIcon: true }, line: spec.line, heads: spec.heads, ...motion, ...role === void 0 ? {} : { role } });
110574
110890
  }
110575
110891
  function parseOverlayStatement(cursor, state, line3, parent) {
110576
110892
  cursor.next();
@@ -110611,6 +110927,7 @@ var init_flow_statements = __esm({
110611
110927
  init_constants();
110612
110928
  init_core();
110613
110929
  init_auto_icon();
110930
+ init_label_icon();
110614
110931
  init_group_options();
110615
110932
  init_line_motion();
110616
110933
  init_line_role();
@@ -110757,6 +111074,7 @@ function resolveEdges(state) {
110757
111074
  to: toNodeId(edge.to),
110758
111075
  label: edge.label,
110759
111076
  icon: edge.icon,
111077
+ ...edge.hasOwnIcon === void 0 ? {} : { hasOwnIcon: edge.hasOwnIcon },
110760
111078
  line: edge.line,
110761
111079
  heads: edge.heads,
110762
111080
  ...edge.isAnimated === void 0 ? {} : { isAnimated: edge.isAnimated },
@@ -111159,7 +111477,7 @@ function typesNamedBy(label) {
111159
111477
  function suggestGroupTypes(diagram) {
111160
111478
  const warnings = [];
111161
111479
  for (const group2 of diagram.groups) {
111162
- if (group2.type !== null) continue;
111480
+ if (group2.type !== null || group2.isPlain === true) continue;
111163
111481
  const types = typesNamedBy(group2.label);
111164
111482
  const [type] = types;
111165
111483
  if (types.length !== 1 || type === void 0) continue;
@@ -111177,7 +111495,6 @@ function suggestGroupTypes(diagram) {
111177
111495
  }
111178
111496
  return warnings;
111179
111497
  }
111180
- var REGION_CODE;
111181
111498
  var init_suggest_group_types = __esm({
111182
111499
  "../../packages/diagrams/src/lint/suggest-group-types.ts"() {
111183
111500
  "use strict";
@@ -111185,7 +111502,53 @@ var init_suggest_group_types = __esm({
111185
111502
  init_core();
111186
111503
  init_icons();
111187
111504
  init_suggest_icons();
111188
- REGION_CODE = /^(?:(?:us|eu|ap|sa|ca|me|af|il|mx)-[a-z]+-\d|(?:us|europe|asia|australia|southamerica|northamerica|me|africa)-[a-z]+\d)$/;
111505
+ }
111506
+ });
111507
+
111508
+ // ../../packages/diagrams/src/lint/suggest-line-roles.ts
111509
+ function nameOf(edge) {
111510
+ return `${edge.from} -> ${edge.to}`;
111511
+ }
111512
+ function suggestLineRoles(diagram) {
111513
+ const byRole = /* @__PURE__ */ new Map();
111514
+ for (const edge of diagram.edges) {
111515
+ if (edge.role !== void 0 || edge.hasOwnIcon === true || edge.label === null) continue;
111516
+ const verb = verbNamedBy(edge.label);
111517
+ const role = verb === null ? null : EDGE_STYLES[verb].role;
111518
+ if (role !== null) byRole.set(role, [...byRole.get(role) ?? [], edge]);
111519
+ }
111520
+ return LINE_ROLES.flatMap((role) => {
111521
+ const edges = byRole.get(role) ?? [];
111522
+ if (edges.length === 0) return [];
111523
+ const named = edges.slice(0, NAMED_ARROWS).map(nameOf).join(", ");
111524
+ const rest = edges.length > NAMED_ARROWS ? ` and ${edges.length - NAMED_ARROWS} more` : "";
111525
+ const doing = edges.length === 1 ? `1 arrow ${ROLE_DOING[role].one}` : `${edges.length} arrows ${ROLE_DOING[role].many}`;
111526
+ const spec = LINE_ROLE_SPECS[role];
111527
+ return [
111528
+ warningAt(
111529
+ DIAGNOSTIC_CODES.suggestLineRole,
111530
+ `${doing} (${named}${rest}) with no role=`,
111531
+ HEADER,
111532
+ edges.length === 1 ? `Add role=${role} to it, which draws the line ${spec.tone} and names it ${spec.legend} in the legend.` : `Add role=${role} to each, which draws the lines ${spec.tone} and names them ${spec.legend} in the legend.`
111533
+ )
111534
+ ];
111535
+ });
111536
+ }
111537
+ var ROLE_DOING, NAMED_ARROWS;
111538
+ var init_suggest_line_roles = __esm({
111539
+ "../../packages/diagrams/src/lint/suggest-line-roles.ts"() {
111540
+ "use strict";
111541
+ init_constants();
111542
+ init_core();
111543
+ init_label_icon();
111544
+ init_general_warnings();
111545
+ ROLE_DOING = {
111546
+ data: { one: "reads or writes stored data", many: "read or write stored data" },
111547
+ async: { one: "sends or receives a message that is not waited for", many: "send or receive messages that are not waited for" },
111548
+ identity: { one: "signs in or carries a credential", many: "sign in or carry credentials" },
111549
+ private: { one: "crosses a private network", many: "cross a private network" }
111550
+ };
111551
+ NAMED_ARROWS = 4;
111189
111552
  }
111190
111553
  });
111191
111554
 
@@ -111226,7 +111589,7 @@ function lintFlow(diagram, measure) {
111226
111589
  shortenedWarning(shortenedIds3(diagram)),
111227
111590
  widthWarning(measure.size, "Use direction=down, shorten labels, or split the diagram.")
111228
111591
  ]);
111229
- return [...general, ...overlayWarnings(diagram, measure.overlayCovers), ...suggestGroupTypes(diagram), ...suggestIcons(diagram)];
111592
+ return [...general, ...overlayWarnings(diagram, measure.overlayCovers), ...suggestGroupTypes(diagram), ...suggestIcons(diagram), ...suggestLineRoles(diagram)];
111230
111593
  }
111231
111594
  var init_lint3 = __esm({
111232
111595
  "../../packages/diagrams/src/kinds/flow/lint.ts"() {
@@ -111240,6 +111603,7 @@ var init_lint3 = __esm({
111240
111603
  init_overlay_warnings();
111241
111604
  init_suggest_group_types();
111242
111605
  init_suggest_icons();
111606
+ init_suggest_line_roles();
111243
111607
  }
111244
111608
  });
111245
111609
 
@@ -112835,7 +113199,7 @@ function matches(rule, words2) {
112835
113199
  return rule.whole.includes(whole2) || rule.last.includes(last) || words2.some((word2) => rule.product.includes(word2));
112836
113200
  }
112837
113201
  function keywordType(label) {
112838
- const words2 = wordsOf2(label);
113202
+ const words2 = wordsOf3(label);
112839
113203
  if (words2.length === 0 || words2.length > KEYWORD_MAX_WORDS) return null;
112840
113204
  const found = new Set(KEYWORD_RULES.filter((rule) => matches(rule, words2)).map((rule) => rule.type));
112841
113205
  const [only] = [...found];
@@ -112847,7 +113211,7 @@ function statusCodeTone(label) {
112847
113211
  return family === "4" || family === "5" ? "red" : null;
112848
113212
  }
112849
113213
  function productType(text3) {
112850
- const words2 = wordsOf2(text3);
113214
+ const words2 = wordsOf3(text3);
112851
113215
  if (words2.length === 0 || words2.length > KEYWORD_MAX_WORDS) return null;
112852
113216
  const found = new Set(KEYWORD_RULES.filter((rule) => words2.some((word2) => rule.product.includes(word2))).map((rule) => rule.type));
112853
113217
  const [only] = [...found];
@@ -112884,7 +113248,7 @@ function inferNodeIcon(node3, type) {
112884
113248
  if (NOT_PRODUCT_TYPES2.has(type) || FLOW_NODE_TYPE_DEFAULTS[type].shape === "pill") return null;
112885
113249
  return brandMarkNamedBy(node3.label) ?? (node3.sub === null ? null : brandMarkNamedBy(node3.sub));
112886
113250
  }
112887
- var wordsOf2, STATUS_CODE, NOT_PRODUCT_TYPES2;
113251
+ var wordsOf3, STATUS_CODE, NOT_PRODUCT_TYPES2;
112888
113252
  var init_infer = __esm({
112889
113253
  "../../packages/diagrams/src/import/infer.ts"() {
112890
113254
  "use strict";
@@ -112892,7 +113256,7 @@ var init_infer = __esm({
112892
113256
  init_icons();
112893
113257
  init_suggest_icons();
112894
113258
  init_mapping();
112895
- wordsOf2 = (label) => label.toLowerCase().split(/[^a-z0-9]+/).filter((word2) => word2 !== "");
113259
+ wordsOf3 = (label) => label.toLowerCase().split(/[^a-z0-9]+/).filter((word2) => word2 !== "");
112896
113260
  STATUS_CODE = /^(?:(?:return|returns|respond|respond with|response|http)\s+)?(?:http\s+)?([2-5])\d\d(?:\s|$)/i;
112897
113261
  NOT_PRODUCT_TYPES2 = /* @__PURE__ */ new Set(["decision", "terminal", "step"]);
112898
113262
  }
@@ -116344,7 +116708,7 @@ function summarizeGit(diagram) {
116344
116708
  const counts = `${countOf(diagram.commits.length, "commit")} on ${countOf(diagram.branches.length, "branch", "branches")}${optionalCount(merges, "merge")}${optionalCount(picks2, "cherry-pick")}${optionalCount(tags, "tag")}`;
116345
116709
  return `${title}: ${counts}.${description}`;
116346
116710
  }
116347
- function nameOf(commit) {
116711
+ function nameOf2(commit) {
116348
116712
  return commit.message === null ? `commit ${commit.index + 1}` : `"${commit.message}"`;
116349
116713
  }
116350
116714
  function describeCommit(commit, diagram) {
@@ -116358,7 +116722,7 @@ function describeCommit(commit, diagram) {
116358
116722
  return `merge of ${commit.mergedBranch ?? "a branch"} into ${commit.branch}${message2}${nothing2}${kind}${id}${tags}`;
116359
116723
  }
116360
116724
  if (commit.origin === "cherry-pick") {
116361
- const of = source === void 0 ? "a commit" : `${source.id ?? nameOf(source)}${source.message === null ? "" : ` ("${source.message}")`}`;
116725
+ const of = source === void 0 ? "a commit" : `${source.id ?? nameOf2(source)}${source.message === null ? "" : ` ("${source.message}")`}`;
116362
116726
  const own2 = commit.message !== null && commit.message !== source?.message ? `, message "${commit.message}"` : "";
116363
116727
  return `cherry-pick of ${of}${own2}${kind}${id}${tags}`;
116364
116728
  }
@@ -116367,7 +116731,7 @@ function describeCommit(commit, diagram) {
116367
116731
  function describeBranch(branch, diagram) {
116368
116732
  const parent = branch.forkedFrom === null ? void 0 : diagram.commits[branch.forkedFrom];
116369
116733
  if (parent === void 0 || branch.parentBranch === null) return `${branch.name} is the default branch`;
116370
- return `${branch.name} was made from ${branch.parentBranch} at ${nameOf(parent)}`;
116734
+ return `${branch.name} was made from ${branch.parentBranch} at ${nameOf2(parent)}`;
116371
116735
  }
116372
116736
  function outlineGit(diagram) {
116373
116737
  const lines = [summarizeGit(diagram), "Branches, in lane order:"];
@@ -117196,7 +117560,7 @@ var init_check2 = __esm({
117196
117560
  });
117197
117561
 
117198
117562
  // ../../packages/diagrams/src/kinds/journey/layout-columns.ts
117199
- function wordsOf3(text3, role) {
117563
+ function wordsOf4(text3, role) {
117200
117564
  const widths = (text3 ?? "").split(/\s+/).filter(Boolean).map((word2) => measureRole(word2, role));
117201
117565
  return { widths, space: measureRole(" ", role), longest: widths.reduce((most, width) => Math.max(most, width), 0) };
117202
117566
  }
@@ -117236,7 +117600,7 @@ function candidates(needed) {
117236
117600
  }
117237
117601
  function chooseColumn(sections, hasTabs) {
117238
117602
  const tasks = sections.flatMap((section) => section.tasks);
117239
- const words2 = tasks.map((task) => ({ label: wordsOf3(task.label, "label"), sub: wordsOf3(task.sub, "sub") }));
117603
+ const words2 = tasks.map((task) => ({ label: wordsOf4(task.label, "label"), sub: wordsOf4(task.sub, "sub") }));
117240
117604
  const longest = words2.reduce((most, item) => Math.max(most, item.label.longest, item.sub.longest), 0);
117241
117605
  const widths = candidates(longest + card.paddingX * 2);
117242
117606
  const isClean = (column) => {
@@ -117545,11 +117909,11 @@ function assignTones(chosen) {
117545
117909
  return given2;
117546
117910
  });
117547
117911
  }
117548
- function wordsOf4(name2) {
117912
+ function wordsOf5(name2) {
117549
117913
  return (name2.match(WORD) ?? []).map((word2) => [...word2]);
117550
117914
  }
117551
117915
  function initialsCandidates(name2) {
117552
- const words2 = wordsOf4(name2);
117916
+ const words2 = wordsOf5(name2);
117553
117917
  const first = words2[0] ?? [];
117554
117918
  if (first.length === 0) return [[...name2.trim()][0]?.toUpperCase() ?? "?"];
117555
117919
  const upper = (chars) => chars.join("").toUpperCase();
@@ -119183,7 +119547,7 @@ function outlineLayers(diagram) {
119183
119547
  ...diagram.links.map((link2) => `- ${describeLink(link2, labels2)}`)
119184
119548
  ];
119185
119549
  }
119186
- var STYLE_WORDS, nameOf2, listOf;
119550
+ var STYLE_WORDS, nameOf3, listOf;
119187
119551
  var init_outline6 = __esm({
119188
119552
  "../../packages/diagrams/src/kinds/layers/outline.ts"() {
119189
119553
  "use strict";
@@ -119191,8 +119555,8 @@ var init_outline6 = __esm({
119191
119555
  init_check3();
119192
119556
  init_text3();
119193
119557
  STYLE_WORDS = { solid: "", dashed: ", optional or indirect", dotted: ", asynchronous" };
119194
- nameOf2 = (block) => block.sub === null ? oneLine2(block.label) : `${oneLine2(block.label)} (${oneLine2(block.sub)})`;
119195
- listOf = (blocks2) => blocks2.length === 0 ? "no blocks." : `${blocks2.map(nameOf2).join(", ")}.`;
119558
+ nameOf3 = (block) => block.sub === null ? oneLine2(block.label) : `${oneLine2(block.label)} (${oneLine2(block.sub)})`;
119559
+ listOf = (blocks2) => blocks2.length === 0 ? "no blocks." : `${blocks2.map(nameOf3).join(", ")}.`;
119196
119560
  }
119197
119561
  });
119198
119562
 
@@ -125664,21 +126028,21 @@ var init_marks2 = __esm({
125664
126028
  });
125665
126029
 
125666
126030
  // ../../packages/diagrams/src/kinds/sankey/node-content.ts
125667
- function nameOf3(node3) {
126031
+ function nameOf4(node3) {
125668
126032
  const name2 = oneLine6(node3.label);
125669
126033
  const chars = [...name2];
125670
126034
  return chars.length > MAX_NAME_CHARS ? { text: chars.slice(0, MAX_NAME_CHARS).join(""), isClipped: true } : { text: name2, isClipped: false };
125671
126035
  }
125672
126036
  function wantedWidth(node3, valueText) {
125673
- const text3 = Math.max(measureRole(nameOf3(node3).text, "label"), valueText === null ? 0 : measureRole(valueText, "sub"));
126037
+ const text3 = Math.max(measureRole(nameOf4(node3).text, "label"), valueText === null ? 0 : measureRole(valueText, "sub"));
125674
126038
  return Math.ceil(2 * SANKEY_METRICS.padX + (node3.icon === null ? 0 : SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap) + text3);
125675
126039
  }
125676
126040
  function floorWidth(node3, valueText) {
125677
- const longest = nameOf3(node3).text.split(" ").reduce((most, word2) => Math.max(most, measureRole(word2, "label")), 0);
126041
+ const longest = nameOf4(node3).text.split(" ").reduce((most, word2) => Math.max(most, measureRole(word2, "label")), 0);
125678
126042
  return Math.ceil(2 * SANKEY_METRICS.padX + (node3.icon === null ? 0 : SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap) + Math.max(longest, valueText === null ? 0 : measureRole(valueText, "sub")));
125679
126043
  }
125680
126044
  function fitNode(node3, valueText, width) {
125681
- const name2 = nameOf3(node3);
126045
+ const name2 = nameOf4(node3);
125682
126046
  const hasChip = node3.icon !== null;
125683
126047
  const textWidth3 = width - 2 * SANKEY_METRICS.padX - (hasChip ? SANKEY_METRICS.chipSize + SANKEY_METRICS.chipGap : 0);
125684
126048
  const fitted = fitText(name2.text, "label", textWidth3, textWidth3, SANKEY_METRICS.maxLabelLines);
@@ -130824,7 +131188,7 @@ function planState(ir, ctx) {
130824
131188
  origin: node3.span.line
130825
131189
  }));
130826
131190
  const kindOf3 = new Map(real.map((node3) => [node3.key, node3.kind]));
130827
- const nameOf10 = (key) => {
131191
+ const nameOf11 = (key) => {
130828
131192
  const marker = ir.nodes.find((node3) => node3.key === key);
130829
131193
  return marker !== void 0 && (marker.kind === "start" || marker.kind === "end") ? `the ${marker.kind} marker` : `"${key}"`;
130830
131194
  };
@@ -130847,7 +131211,7 @@ function planState(ir, ctx) {
130847
131211
  }
130848
131212
  const isNested = transition.from !== transition.to && (lineageOf2(transition.to, parentKeys).includes(transition.from) || lineageOf2(transition.from, parentKeys).includes(transition.to));
130849
131213
  if (isNested) {
130850
- reporter.add(DIAGNOSTIC_CODES.mermaidDroppedMeaning, span, `Dropped a transition between ${nameOf10(transition.from)} and ${nameOf10(transition.to)}, because one is inside the other. A transition cannot join a state to one inside it.`, "Start the transition from a state inside the composite state, or from its own start marker.", "nested-transition");
131214
+ reporter.add(DIAGNOSTIC_CODES.mermaidDroppedMeaning, span, `Dropped a transition between ${nameOf11(transition.from)} and ${nameOf11(transition.to)}, because one is inside the other. A transition cannot join a state to one inside it.`, "Start the transition from a state inside the composite state, or from its own start marker.", "nested-transition");
130851
131215
  continue;
130852
131216
  }
130853
131217
  const { composite, region } = transition.scope;
@@ -130912,7 +131276,7 @@ function summarizeState(diagram) {
130912
131276
  const description = diagram.meta.description === null ? "" : ` ${diagram.meta.description}`;
130913
131277
  return `${title}: ${counts}.${description}`;
130914
131278
  }
130915
- function nameOf4(diagram, node3) {
131279
+ function nameOf5(diagram, node3) {
130916
131280
  const holder = node3.parent === null ? void 0 : diagram.states.find((candidate2) => candidate2.id === node3.parent);
130917
131281
  const scope = holder === void 0 ? "" : ` of ${holder.label}${node3.region === null ? "" : regionSuffix(diagram, holder.id, node3.region)}`;
130918
131282
  if (node3.kind === "start" || node3.kind === "end") return `${KIND_NAMES[node3.kind]}${scope}`;
@@ -130927,7 +131291,7 @@ function regionSuffix(diagram, parent, index2) {
130927
131291
  }
130928
131292
  function nodeLine2(diagram, node3) {
130929
131293
  const detail = node3.description.length === 0 ? "" : ` (${node3.description.join("; ")})`;
130930
- const name2 = nameOf4(diagram, node3);
131294
+ const name2 = nameOf5(diagram, node3);
130931
131295
  if (node3.kind === "state") return `${name2}${detail}, state`;
130932
131296
  if (node3.kind === "composite") return `${name2}${detail}, composite state`;
130933
131297
  if (node3.kind === "choice") return `${name2}, choice`;
@@ -130938,7 +131302,7 @@ function nodeLine2(diagram, node3) {
130938
131302
  function transitionLine(diagram, transition) {
130939
131303
  const name2 = (id) => {
130940
131304
  const node3 = diagram.states.find((candidate2) => candidate2.id === id);
130941
- return node3 === void 0 ? id : nameOf4(diagram, node3);
131305
+ return node3 === void 0 ? id : nameOf5(diagram, node3);
130942
131306
  };
130943
131307
  const on = transition.event === null ? "" : ` on ${transition.event}`;
130944
131308
  const guard = transition.guard === null ? "" : `, if ${transition.guard}`;
@@ -130970,7 +131334,7 @@ function outlineState(diagram) {
130970
131334
  lines.push("Notes:");
130971
131335
  for (const note of diagram.notes) {
130972
131336
  const target = diagram.states.find((node3) => node3.id === note.target);
130973
- lines.push(`- On ${target === void 0 ? note.target : nameOf4(diagram, target)}: ${note.text}`);
131337
+ lines.push(`- On ${target === void 0 ? note.target : nameOf5(diagram, target)}: ${note.text}`);
130974
131338
  }
130975
131339
  }
130976
131340
  return lines;
@@ -138997,7 +139361,7 @@ api --> email : "Sends email using" tech="SMTP"
138997
139361
  email --> customer : "Sends emails to"
138998
139362
  ` },
138999
139363
  { kind: "class", title: "Notification service", source: 'class "Notification service" desc="A notifier queues messages and delivers each one through a channel. Channels are interchangeable, so a new one needs no change to the notifier."\n\nclass Notifier stereotype="service" {\n "-queue: Queue<Message>"\n "+send(m: Message): void"\n "+retry(): int"\n}\n\nclass Message {\n "+id: uuid"\n "+body: String"\n "+priority: Priority"\n}\n\nclass Priority type=enum {\n "LOW"\n "NORMAL"\n "URGENT"\n}\n\nclass Template "Template<T>" type=abstract {\n "+render(data: T): String" abstract\n "+named(n: String): Template" static\n}\n\npackage channels "Channels" {\n class Channel type=interface {\n "+deliver(m: Message): Ack"\n }\n class Email "EmailChannel" {\n "-host: String"\n "+deliver(m: Message): Ack"\n }\n class Sms "SmsChannel" {\n "-gateway: String"\n "+deliver(m: Message): Ack"\n }\n}\n\nNotifier "1" owns "*" Message : "queues"\nNotifier "1" has "1..*" Channel : "delivers through"\nNotifier --> Template : "renders with"\nMessage --> Priority\nEmail implements Channel\nSms implements Channel\n\nnote on Message : "Kept until every channel has confirmed delivery."\n' },
139000
- { kind: "flow", title: "Login request", source: 'flow "Login request" desc="A user signs in through an API that checks credentials in a database."\n\nnode user person "User"\nnode api service "Auth API" sub="Validates credentials"\n\ngroup data "Data" icon=lucide:database {\n node db database "PostgreSQL" sub="Password hashes"\n node cache cache "Redis" sub="Sessions"\n}\n\nuser -> api : "POST /login"\napi -> db : "look up user"\ndb --> api : "row"\napi -> cache : "store session"\n' },
139364
+ { kind: "flow", title: "Login request", source: 'flow "Login request" desc="A user signs in through an API that checks credentials in a database and keeps the session in a cache."\n\nnode user person "User"\nnode api service "Auth API" sub="Validates credentials"\n\ngroup data "Data" {\n node db database "PostgreSQL" sub="Password hashes"\n node cache cache "Redis" sub="Sessions"\n}\n\nuser -> api : "signs in" role=identity\napi -> db : "looks up user" role=data\ndb --> api : "row"\napi -> cache : "stores session" role=data\n' },
139001
139365
  { kind: "gantt", title: "Product launch plan", source: 'gantt "Product launch plan" start=2026-01-05 unit=week today=2026-02-11 weekends=skip holidays=2026-04-03 desc="A fourteen week plan from research to launch, with design and build running side by side."\n\nsection "Discover" {\n task research "User research" start=2026-01-05 duration=2w status=done\n task brief "Product brief" after=research duration=1w status=done\n}\n\nsection "Design" tone=violet {\n task ux "UX flows" after=brief duration=2w status=done\n task ui "Visual design" after=ux duration=3w status=active\n task ds "Design system" after=brief duration=4w status=active\n}\n\nsection "Build" tone=aqua {\n task api "API and data model" after=brief duration=5w status=active\n task web "Web app" after=ui,api duration=4w status=critical\n task mobile "Mobile app" after=ui,api duration=4w\n milestone beta "Beta ready" after=web\n}\n\nsection "Launch" tone=orange {\n task docs "Help center" after=ui duration=3w\n task qa "Testing and fixes" after=web,mobile duration=2w status=critical\n milestone ga "Public launch" after=qa status=critical\n}\n' },
139002
139366
  { kind: "git", title: "Release and hotfix", source: 'git "Release and hotfix" desc="A feature is merged into develop, develop is released as v1.0, and a hotfix is tagged and picked back."\n\ncommit "Initial commit"\nbranch develop order=2\ncommit "Set up CI"\nbranch "feature/search" from=develop\ncommit "Add search box"\ncommit "Index the docs"\ncheckout develop\nmerge "feature/search" "Add search"\ncheckout main\nmerge develop "Release 1.0" tag="v1.0" type=highlight\nbranch hotfix order=1\ncommit "Fix null check" id=fix\ncheckout main\nmerge hotfix tag="v1.0.1"\ncheckout develop\ncherry-pick fix\ncommit "Revert flaky test" type=reverse\n' },
139003
139367
  { kind: "journey", title: "Checkout experience", source: 'journey "Checkout experience" desc="How a shopper feels from finding a product to holding the parcel, and who is involved at each step."\n\nactor "Customer" tone=blue\nactor "Support agent" tone=violet\nactor "Bank"\nactor "Courier"\n\nsection "Browse" {\n task "Find a product" score=5 actors="Customer"\n task "Compare prices" score=3 actors="Customer, Support agent" sub="Asks about delivery"\n}\n\nsection "Pay" {\n task "Enter card details" score=2 actors="Customer"\n task "Confirm with the bank" score=1 actors="Customer, Bank"\n task "Get a receipt" score=4 actors="Customer"\n}\n\nsection "Deliver" {\n task "Track the parcel" score=4 actors="Customer, Courier"\n task "Receive it" score=5 actors="Customer, Courier"\n}\n' },
@@ -139055,7 +139419,7 @@ ship -> delivered : "arrived"
139055
139419
  { kind: "treemap", title: "Monthly cloud spend", source: 'treemap "Monthly cloud spend" unit="$" better=down desc="Where the monthly cloud bill goes, by service, with the change since last month. A rise in cost is drawn red."\n\ngroup "Compute" icon=lucide:cpu {\n item "EC2" 18400 icon=aws:ec2 delta="8%"\n item "Fargate" 6800 icon=aws:fargate delta="21%"\n item "Lambda" 4200 icon=aws:lambda delta="-12%"\n item "EKS" 3100 icon=aws:eks\n}\n\ngroup "Data" icon=lucide:database {\n item "RDS" 9600 icon=aws:rds delta="4%"\n item "S3" 5200 icon=aws:s3 delta="-3%"\n item "DynamoDB" 3900 icon=aws:dynamodb\n item "OpenSearch" 3400 icon=aws:opensearch\n item "ElastiCache" 2800\n}\n\ngroup "Network" icon=lucide:globe {\n item "Data transfer" 4800 delta="15%"\n item "NAT gateway" 3300 icon=aws:nat-gateway\n item "CloudFront" 2200 icon=aws:cloudfront delta="-6%"\n item "Load balancers" 1900 icon=aws:elb\n}\n\ngroup "Operations" icon=lucide:activity {\n item "CloudWatch" 4600 icon=aws:cloudwatch\n item "Support plan" 3200\n group "Security" icon=lucide:shield {\n item "GuardDuty" 2400 icon=aws:guardduty\n item "WAF" 1900 icon=aws:waf\n }\n}\n' },
139056
139420
  { kind: "xychart", title: "API latency against the SLO", source: 'xychart "API latency against the SLO" desc="Weekly p50 and p95 latency of the checkout API against its 400 ms objective: p95 crossed the line after release 2.3 and came back under it after the cache fix."\n\nx "Week" ["W1" "W2" "W3" "W4" "W5" "W6" "W7" "W8"]\ny "Latency" unit="ms" min=0 max=500\n\nbar "p95" [310 330 345 420 455 440 350 335]\nline "p50" [120 125 128 160 170 165 135 130]\n\nthreshold "SLO 400 ms" 400 tone=red\n\nnote "Release 2.3" at="W4"\nnote "Cache fix" at="W7"\n' }
139057
139421
  ];
139058
- ARCHITECTURE_EXAMPLE = { kind: "flow", title: "Web application on a cloud", source: 'flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry."\n\nnode visitors client "Visitors" sub="Browsers"\n\ngroup edge "Edge" type=edge {\n node cdn gateway "Content delivery" icon=azure:cdn\n}\n\ngroup sign-in "Sign-in" type=identity-boundary {\n node idp identity "Identity service" icon=azure:entra-id\n}\n\ngroup files "Storage" type=storage-account {\n node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript\n node uploads storage "Uploads" icon=azure:blob-storage\n}\n\ngroup plan "Application plan" type=service-plan {\n node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs\n}\n\ngroup messaging "Messaging" type=messaging {\n node jobs queue "Job queue" icon=azure:service-bus\n}\n\nnode worker function "Background worker" icon=azure:functions stack=simple:nodedotjs\nnode db database "Database" icon=azure:sql-database\n\ngroup delivery "Delivery pipeline" type=ci-cd {\n node repo step "Source code" icon=lucide:git-branch\n node build step "Build pipeline" icon=azure:devops\n}\n\ngroup monitoring "Monitoring" type=observability {\n node telemetry service "Metrics and logs" icon=azure:monitor\n}\n\nvisitors -> cdn : "HTTPS"\nvisitors -> idp : "sign in"\ncdn -> site\ncdn -> api : "API calls"\napi -> uploads : "store files"\napi -> jobs : "enqueue"\njobs ..> worker : "event"\nworker -> db : "writes"\napi -> db : "reads"\nrepo -> build\nbuild ..> api : "deploys image"\napi ..> telemetry : "telemetry"\n' };
139422
+ ARCHITECTURE_EXAMPLE = { kind: "flow", title: "Web application on a cloud", source: 'flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry."\n\nnode visitors client "Visitors" sub="Browsers"\n\ngroup edge "Edge" type=edge {\n node cdn gateway "Content delivery" icon=azure:cdn\n}\n\ngroup sign-in "Sign-in" type=identity-boundary {\n node idp identity "Identity service" icon=azure:entra-id\n}\n\ngroup files "Storage" type=storage-account {\n node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript\n node uploads storage "Uploads" icon=azure:blob-storage\n}\n\ngroup plan "Application plan" type=service-plan {\n node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs\n}\n\ngroup messaging "Messaging" type=messaging {\n node jobs queue "Job queue" icon=azure:service-bus\n}\n\nnode worker function "Background worker" icon=azure:functions stack=simple:nodedotjs\nnode db database "Database" icon=azure:sql-database\n\ngroup delivery "Delivery pipeline" type=ci-cd {\n node repo step "Source code" icon=lucide:git-branch\n node build step "Build pipeline" icon=azure:devops\n}\n\ngroup monitoring "Monitoring" type=observability {\n node telemetry service "Metrics and logs" icon=azure:monitor\n}\n\nvisitors -> cdn : "HTTPS"\nvisitors -> idp : "sign in" role=identity\ncdn -> site\ncdn -> api : "API calls"\napi -> uploads : "store files" role=data\napi -> jobs : "enqueue" role=async\njobs ..> worker : "event"\nworker -> db : "writes" role=data\napi -> db : "reads" role=data\nrepo -> build\nbuild ..> api : "deploys image"\napi ..> telemetry : "telemetry"\n' };
139059
139423
  }
139060
139424
  });
139061
139425
 
@@ -139107,6 +139471,8 @@ function buildVocabulary() {
139107
139471
  },
139108
139472
  lineOptions: LINE_OPTION_SPECS.map((spec) => ({ ...spec })),
139109
139473
  labelIcons: Object.entries(LABEL_ICON_DEFAULTS).map(([label, icon]) => ({ label, icon })),
139474
+ verbIcons: Object.entries(LABEL_VERB_PHRASES).map(([verb, words2]) => ({ icon: EDGE_STYLES[verb].icon, words: [...words2 ?? []] })),
139475
+ style: [...STYLE_STEPS],
139110
139476
  autoIcons: { ...ICON_MODE_SPEC },
139111
139477
  roleIcons: roleIconTable(),
139112
139478
  statusIcons: Object.entries(STATUS_ICON_SPECS).map(([name2, spec]) => ({ ref: `status:${name2}`, tone: spec.tone, meaning: spec.meaning })),
@@ -139215,6 +139581,8 @@ var init_build_vocabulary = __esm({
139215
139581
  arrows: ["overview", "flow", "sequence"],
139216
139582
  lineOptions: ["overview", "flow", "sequence"],
139217
139583
  labelIcons: ["overview", "flow"],
139584
+ verbIcons: ["flow"],
139585
+ style: ["overview", "flow"],
139218
139586
  autoIcons: AUTO_ICON_KINDS,
139219
139587
  roleIcons: ["flow", "c4", "layers", "mindmap", "swimlane"],
139220
139588
  blockTypes: ["overview", "sequence"],
@@ -139835,7 +140203,7 @@ function descendantsOf(scan, index2) {
139835
140203
  });
139836
140204
  return inside;
139837
140205
  }
139838
- function nameOf5(element) {
140206
+ function nameOf6(element) {
139839
140207
  if (element.role === "edge") return `${element.from ?? ""} ${element.arrow ?? ""} ${element.to ?? ""}`;
139840
140208
  return element.id ?? (element.label === null ? element.rest : `"${element.label}"`);
139841
140209
  }
@@ -139859,7 +140227,7 @@ function subjectOf(element) {
139859
140227
  function renamedName(element, rename2) {
139860
140228
  if (element.role === "edge") return `${rename2(element.from ?? "")} -> ${rename2(element.to ?? "")}`;
139861
140229
  if (element.role === "header") return "header";
139862
- return element.id === null ? nameOf5(element) : rename2(element.id);
140230
+ return element.id === null ? nameOf6(element) : rename2(element.id);
139863
140231
  }
139864
140232
  function entriesOf3(source, rename2) {
139865
140233
  const { elements } = scanSource(source);
@@ -178587,6 +178955,8 @@ var init_vocabulary18 = __esm({
178587
178955
  group: GroupSchema.describe("How a flow group looks and what it accepts; its corner tab shows its icon and name."),
178588
178956
  lineOptions: external_exports.array(LineOptionSchema).describe("Options that animate a line (animate=on makes dashes travel toward the arrowhead) or number the steps of a flow or sequence (numbered=on, start=, and step= on a line). All are off by default."),
178589
178957
  labelIcons: external_exports.array(LabelIconSchema).describe("Edge labels that get a tick or cross automatically; icon=none on the edge removes it."),
178958
+ verbIcons: external_exports.array(external_exports.object({ icon: external_exports.string().describe("The icon drawn in the label."), words: external_exports.array(external_exports.string()).describe('Phrases that draw it: the whole label, or its first words followed by what is acted on ("reads orders").') })).describe("The icons a flow edge label draws when it names a relationship with a verb. icon= on the edge wins, icon=none removes it, and icons=off on the header turns them all off."),
178959
+ style: external_exports.array(external_exports.string()).describe("How to make a flow diagram look finished: verbs on arrows, role= on lines that mean something, typed groups, product names."),
178590
178960
  autoIcons: AutoIconsSchema.describe("Items with no icon= get the icon their label names for certain (a brand, a cloud product, a common role). icon=none keeps one item plain; icons=off on the header keeps them all plain."),
178591
178961
  roleIcons: external_exports.array(external_exports.object({ icon: external_exports.string().describe("The icon drawn."), words: external_exports.array(external_exports.string()).describe("Whole words of a label of three words or fewer that draw it.") })).describe("The plain icons for common roles, drawn on flow and sequence service and external items, C4 elements, layers blocks, mind map topics, and swimlane lanes. The last role word of the label wins."),
178592
178962
  statusIcons: external_exports.array(StatusIconSchema).describe("Colored status and outcome icons: a green check, a red X, and so on."),
@@ -179206,7 +179576,7 @@ function finding(level2, code, message2, at3, hint2) {
179206
179576
  function appliesTo(rule, kind) {
179207
179577
  return rule.kinds === void 0 || rule.kinds.includes(kind);
179208
179578
  }
179209
- function nameOf6(subject) {
179579
+ function nameOf7(subject) {
179210
179580
  return subject.id === null || subject.id === subject.label ? `"${subject.label}"` : `${subject.id} ("${subject.label}")`;
179211
179581
  }
179212
179582
  function iconAccepted(icon, choices) {
@@ -179233,7 +179603,7 @@ function iconFindings(rule, subjects, level2, places) {
179233
179603
  const drawn = subject.icon === null ? "draws no icon" : `draws ${subject.icon}`;
179234
179604
  const [first] = rule.use;
179235
179605
  const choices = rule.use.length === 1 ? `icon=${first ?? ""}` : `icon= with one of ${rule.use.join(", ")}`;
179236
- return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleIcon, `${nameOf6(subject)} matches /${rule.label}/ and ${drawn}`, places.of(subject), rule.hint ?? `Give it ${choices}.`)];
179606
+ return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleIcon, `${nameOf7(subject)} matches /${rule.label}/ and ${drawn}`, places.of(subject), rule.hint ?? `Give it ${choices}.`)];
179237
179607
  });
179238
179608
  }
179239
179609
  function toneFindings(rule, subjects, level2, places) {
@@ -179243,7 +179613,7 @@ function toneFindings(rule, subjects, level2, places) {
179243
179613
  if (!broken) return [];
179244
179614
  const of = rule.types === void 0 ? "a node" : `a ${subject.type}`;
179245
179615
  const fix = rule.allow !== void 0 ? `Use tone=${rule.allow.join(" or tone=")}.` : `Use a tone other than ${(rule.forbid ?? []).join(", ")}.`;
179246
- return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleTone, `${nameOf6(subject)} is ${subject.tone}, a tone the team does not use for ${of}`, places.of(subject), rule.hint ?? fix)];
179616
+ return [finding(rule.level ?? level2, DIAGNOSTIC_CODES.ruleTone, `${nameOf7(subject)} is ${subject.tone}, a tone the team does not use for ${of}`, places.of(subject), rule.hint ?? fix)];
179247
179617
  });
179248
179618
  }
179249
179619
  function diagramFindings(diagram, rules, count3, level2, places) {
@@ -179536,9 +179906,9 @@ users -> ingress : "HTTPS"
179536
179906
  ingress -> web
179537
179907
  ingress -> api
179538
179908
  ingress -> admin
179539
- api -> config : "reads"
179909
+ api -> config : "reads" icon=lucide:eye
179540
179910
  web ..> monitor : "metrics"
179541
- api -> secrets : "reads"`;
179911
+ api -> secrets : "reads secrets" role=identity`;
179542
179912
  NEUTRAL2 = `flow "{{title}}" direction=down desc="{{description}}"
179543
179913
 
179544
179914
  group delivery "{{pipeline}}" type=ci-cd {
@@ -179844,8 +180214,8 @@ bus ..> processor : "all events"
179844
180214
  bus ..> notifier : "alerts"
179845
180215
  bus ..> dlq : "after retries"
179846
180216
  archiver -> archive : "archive"
179847
- processor -> db : "write"
179848
- notifier ..> mail : "send"`;
180217
+ processor -> db : "write" role=data
180218
+ notifier ..> mail : "send" role=async`;
179849
180219
  NEUTRAL5 = `flow "{{title}}" direction=down desc="{{description}}"
179850
180220
 
179851
180221
  group producers "{{producers}}" type=app-tier {
@@ -180073,7 +180443,7 @@ var init_microservices_gateway = __esm({
180073
180443
  { id: "billing-database", default: "Billing database", meaning: "The database that only the billing service reads and writes." }
180074
180444
  ];
180075
180445
  EDGES6 = `clients -> gateway : "HTTPS"
180076
- gateway -> idp : "verify token"
180446
+ gateway -> idp : "verify token" role=identity
180077
180447
  gateway -> catalog : "{{catalog-path}}"
180078
180448
  gateway -> booking : "{{booking-path}}"
180079
180449
  catalog -> catalog-db
@@ -180223,12 +180593,12 @@ var init_queue_worker = __esm({
180223
180593
  { id: "alarm", default: "Queue depth alarm", meaning: "The alarm that fires when too many jobs wait in the queue." }
180224
180594
  ];
180225
180595
  EDGES7 = `client -> api : "{{submit}}"
180226
- api ..> queue : "enqueue job"
180596
+ api ..> queue : "enqueue job" role=async
180227
180597
  queue ..> w1
180228
180598
  queue ..> w2
180229
180599
  queue ..> w3
180230
180600
  queue ..> alarm
180231
- w2 -> db : "write result"
180601
+ w2 -> db : "write result" role=data
180232
180602
  w3 ..> dlq : "after {{retries}} tries"`;
180233
180603
  OVERLAY = 'overlay pool "{{pool}}" type=scale-group members=w1,w2,w3';
180234
180604
  NEUTRAL9 = `flow "{{title}}" direction=down desc="{{description}}"
@@ -180325,9 +180695,9 @@ var init_serverless_api = __esm({
180325
180695
  gateway -> create : "POST {{path}}"
180326
180696
  gateway -> list : "GET {{path}}"
180327
180697
  gateway -> get : "GET {{path}}/id"
180328
- gateway -> auth : "verify token"
180329
- create -> bucket : "upload"
180330
- create -> db : "write"
180698
+ gateway -> auth : "verify token" role=identity
180699
+ create -> bucket : "upload" role=data
180700
+ create -> db : "write" role=data
180331
180701
  list -> db
180332
180702
  get -> db
180333
180703
  get ..> monitor : "logs and metrics"`;
@@ -180521,7 +180891,7 @@ dns -> cdn
180521
180891
  cert -> cdn
180522
180892
  publish ..> cdn
180523
180893
  cdn -> bucket : "fetch files"
180524
- publish -> bucket : "upload"`;
180894
+ publish -> bucket : "upload" role=data`;
180525
180895
  NEUTRAL12 = `flow "{{title}}" direction=down desc="{{description}}"
180526
180896
 
180527
180897
  group pipeline "{{pipeline}}" type=ci-cd {
@@ -180905,14 +181275,14 @@ var init_web_app_on_cloud = __esm({
180905
181275
  { id: "telemetry", default: "Metrics and logs", meaning: "The service that collects what the API reports." }
180906
181276
  ];
180907
181277
  EDGES11 = `visitors -> cdn : "HTTPS"
180908
- visitors -> idp : "sign in"
181278
+ visitors -> idp : "sign in" role=identity
180909
181279
  cdn -> site
180910
181280
  cdn -> api : "API calls"
180911
- api -> uploads : "store files"
180912
- api -> jobs : "enqueue"
181281
+ api -> uploads : "store files" role=data
181282
+ api -> jobs : "enqueue" role=async
180913
181283
  jobs ..> worker : "event"
180914
- worker -> db : "writes"
180915
- api -> db : "reads"
181284
+ worker -> db : "writes" role=data
181285
+ api -> db : "reads" role=data
180916
181286
  repo -> build
180917
181287
  build ..> api : "deploys image"
180918
181288
  api ..> telemetry : "telemetry"`;
@@ -181196,6 +181566,14 @@ var init_direction = __esm({
181196
181566
  }
181197
181567
  });
181198
181568
 
181569
+ // ../../packages/diagrams/src/importers/shared/edge-style.ts
181570
+ var init_edge_style = __esm({
181571
+ "../../packages/diagrams/src/importers/shared/edge-style.ts"() {
181572
+ "use strict";
181573
+ init_constants();
181574
+ }
181575
+ });
181576
+
181199
181577
  // ../../packages/diagrams/src/importers/shared/icons.ts
181200
181578
  function known2(ref) {
181201
181579
  return ref !== null && isKnownIcon(ref) ? ref : null;
@@ -181688,7 +182066,7 @@ function writeOneFlow(draft, report3) {
181688
182066
  const children = (parent) => groups.filter((group2) => parentOf4(group2) === parent);
181689
182067
  const writeGroup = (group2, depth) => {
181690
182068
  lines.gap();
181691
- lines.add(`group ${ids.idFor(`group:${group2.key}`)} ${quote(group2.label)}${options2({ type: group2.type ?? namedType(group2.label), icon: known2(group2.icon ?? null), sub: group2.sub ?? null, tone: group2.tone ?? null })} {`, depth);
182069
+ lines.add(`group ${ids.idFor(`group:${group2.key}`)} ${quote(group2.label)}${options2({ type: group2.type ?? namedType(group2.label) ?? group2.otherwise ?? null, icon: known2(group2.icon ?? null), sub: group2.sub ?? null, tone: group2.tone ?? null })} {`, depth);
181692
182070
  for (const node3 of kept2) if ((node3.group ?? null) === group2.key) nodeLine4(node3, depth + 1);
181693
182071
  for (const child of children(group2.key)) writeGroup(child, depth + 1);
181694
182072
  lines.add("}", depth);
@@ -181698,16 +182076,24 @@ function writeOneFlow(draft, report3) {
181698
182076
  for (const node3 of kept2) if (node3.group === void 0 || node3.group === null || !groupKeys.has(node3.group)) nodeLine4(node3, 0);
181699
182077
  for (const group2 of children(null)) writeGroup(group2, 0);
181700
182078
  const written3 = /* @__PURE__ */ new Set();
181701
- let links = 0;
181702
- lines.gap();
182079
+ const drawn = [];
181703
182080
  for (const edge of draft.edges) {
181704
182081
  const pair2 = `${edge.from}\0${edge.to}`;
181705
182082
  if (edge.from === edge.to || !keptKeys.has(edge.from) || !keptKeys.has(edge.to) || written3.has(pair2)) continue;
181706
182083
  written3.add(pair2);
181707
- links += 1;
181708
- const label = edge.label === void 0 || edge.label === null || edge.label === "" ? "" : ` : ${quote(edge.label)}`;
181709
- lines.add(`${ids.idFor(`node:${edge.from}`)} ${edge.arrow ?? "->"} ${ids.idFor(`node:${edge.to}`)}${label}`);
182084
+ drawn.push(edge);
182085
+ }
182086
+ const roleOf3 = (edge) => edge.verb === void 0 || edge.verb === null ? null : EDGE_STYLES[edge.verb].role;
182087
+ const roles = new Set(drawn.map(roleOf3));
182088
+ const colour = roles.size > 1;
182089
+ lines.gap();
182090
+ for (const edge of drawn) {
182091
+ const hasLabel = edge.label !== void 0 && edge.label !== null && edge.label !== "";
182092
+ const label = hasLabel ? ` : ${quote(edge.label ?? "")}` : "";
182093
+ const style = edge.verb === void 0 || edge.verb === null ? null : EDGE_STYLES[edge.verb];
182094
+ lines.add(`${ids.idFor(`node:${edge.from}`)} ${edge.arrow ?? "->"} ${ids.idFor(`node:${edge.to}`)}${label}${options2({ icon: hasLabel ? known2(style?.icon ?? null) : null, role: colour ? style?.role ?? null : null })}`);
181710
182095
  }
182096
+ const links = drawn.length;
181711
182097
  const coverage = draft.coverage ?? { noun: draft.noun, total: draft.nodes.length, drawn: kept2.length, summarised: 0 };
181712
182098
  const named = { ...ids.mapped("group:"), ...ids.mapped("node:") };
181713
182099
  return { kind: "flow", source: lines.toString(), stats: { items: kept2.length, links, groups: groups.length, dropped: left.length }, advice: draft.advice ?? null, coverage, ids: named };
@@ -181719,6 +182105,7 @@ var init_flow2 = __esm({
181719
182105
  init_core();
181720
182106
  init_lint21();
181721
182107
  init_suggest_icons();
182108
+ init_edge_style();
181722
182109
  init_icons3();
181723
182110
  init_reporter2();
181724
182111
  init_split_flow();
@@ -182090,6 +182477,7 @@ var init_shared2 = __esm({
182090
182477
  "../../packages/diagrams/src/importers/shared/index.ts"() {
182091
182478
  "use strict";
182092
182479
  init_direction();
182480
+ init_edge_style();
182093
182481
  init_flow2();
182094
182482
  init_split_flow();
182095
182483
  init_icons3();
@@ -182677,7 +183065,7 @@ function drawCharts(files, context, index2) {
182677
183065
  const edges = [];
182678
183066
  const repositories = new Set(charts.flatMap((chart) => chart.dependencies.map((dependency) => dependency.repository)).filter((repository) => repository !== null && repository !== "" && !repository.startsWith("file://")));
182679
183067
  const framed = repositories.size > 1;
182680
- const groups = framed ? [...repositories].sort().map((repository) => ({ key: `repository:${repository}`, label: repositoryLabel(repository), sub: "Chart repository", icon: "simple:helm", idHint: repositoryLabel(repository).split("/").pop() ?? "repository" })) : [];
183068
+ const groups = framed ? [...repositories].sort().map((repository) => ({ key: `repository:${repository}`, label: repositoryLabel(repository), sub: "Chart repository", type: "external-systems", icon: "simple:helm", idHint: repositoryLabel(repository).split("/").pop() ?? "repository" })) : [];
182681
183069
  for (const chart of charts) {
182682
183070
  const look = imageLook(chart.name);
182683
183071
  nodes.push({ key: chartKey(chart), type: look?.type ?? "service", label: chart.name, sub: cleanText([[chart.library ? "Library chart" : "Chart", chart.version].filter((part) => part !== null).join(" "), chart.appVersion === null ? null : `app ${chart.appVersion}`].filter((part) => part !== null).join(", ")), icon: look?.icon ?? "simple:helm", idHint: chart.name });
@@ -182695,7 +183083,7 @@ function drawCharts(files, context, index2) {
182695
183083
  nodes.push({ key, type: look?.type ?? "service", label: shown, sub: cleanText([shown === dependency.name ? "Chart" : `Chart ${dependency.name}`, off ? "off in values" : dependency.condition !== null && setting !== "true" ? "optional" : null].filter((part) => part !== null).join(", ")), icon: look?.icon ?? iconForName(dependency.name, look?.type ?? "service") ?? "simple:helm", group: repository, idHint: shown });
182696
183084
  }
182697
183085
  const version2 = chart.locked.get(dependency.name) ?? dependency.version;
182698
- edges.push({ from: chartKey(chart), to: key, label: version2, ...off ? { arrow: "-->" } : {} });
183086
+ edges.push({ from: chartKey(chart), to: key, label: version2, verb: "depends-on", ...off ? { arrow: "-->" } : {} });
182699
183087
  }
182700
183088
  }
182701
183089
  const root = charts[0];
@@ -182911,7 +183299,7 @@ function edgesOf(drawn, folded, context) {
182911
183299
  const from = keyOf(manifest.kind, manifest.namespace, manifest.name);
182912
183300
  for (const route of manifest.routes) {
182913
183301
  const service = keyOf("Service", manifest.namespace, route.service);
182914
- if (has("Service", manifest.namespace, route.service)) edges.push({ from, to: folded.get(service) ?? service, label: route.label });
183302
+ if (has("Service", manifest.namespace, route.service)) edges.push({ from, to: folded.get(service) ?? service, label: route.label, verb: "routes" });
182915
183303
  else unresolved(manifest, `Service ${route.service}`);
182916
183304
  }
182917
183305
  if (manifest.kind === "Service" && !folded.has(from)) {
@@ -182929,7 +183317,7 @@ function edgesOf(drawn, folded, context) {
182929
183317
  }
182930
183318
  }
182931
183319
  for (const use of manifest.uses) {
182932
- if (has(use.kind, manifest.namespace, use.name)) edges.push({ from, to: keyOf(use.kind, manifest.namespace, use.name), arrow: use.kind === "PersistentVolumeClaim" ? "->" : "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads" });
183320
+ if (has(use.kind, manifest.namespace, use.name)) edges.push({ from, to: keyOf(use.kind, manifest.namespace, use.name), arrow: use.kind === "PersistentVolumeClaim" ? "->" : "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads", verb: use.kind === "PersistentVolumeClaim" ? "mounts" : use.kind === "Secret" ? "secret" : "reads-settings" });
182933
183321
  else unresolved(manifest, `${use.kind} ${use.name}`);
182934
183322
  }
182935
183323
  }
@@ -182971,13 +183359,13 @@ function drawManifests(documents, context, framing) {
182971
183359
  const ordered = [...drawn].sort((a, b) => (LOOKS[a.kind]?.rank ?? 2) - (LOOKS[b.kind]?.rank ?? 2));
182972
183360
  const namespaces = [...new Set(ordered.map((manifest) => manifest.namespace))];
182973
183361
  const grouped = namespaces.length > 1;
182974
- const groups = grouped ? namespaces.map((namespace) => ({ key: `ns:${namespace}`, label: namespace, type: namespaceType(namespace), sub: "Namespace", idHint: `ns_${namespace}` })) : [];
183362
+ const groups = grouped ? namespaces.map((namespace) => ({ key: `ns:${namespace}`, label: namespace, type: namespaceType(namespace), otherwise: "resource-group", tone: namespaceType(namespace) === null ? "violet" : null, sub: "Namespace", idHint: `ns_${namespace}` })) : [];
182975
183363
  const framed = /* @__PURE__ */ new Map();
182976
183364
  for (const manifest of framing === null ? [] : ordered) {
182977
183365
  let parent = grouped ? `ns:${manifest.namespace}` : null;
182978
183366
  for (const frame of framing?.framesOf(manifest) ?? []) {
182979
183367
  const key = grouped ? `${manifest.namespace}/${frame.key}` : frame.key;
182980
- if (!groups.some((group2) => group2.key === key)) groups.push({ key, label: frame.label, sub: frame.sub, icon: frame.icon, parent, idHint: frame.idHint });
183368
+ if (!groups.some((group2) => group2.key === key)) groups.push({ key, label: frame.label, sub: frame.sub, icon: frame.icon, otherwise: frame.type, tone: frame.tone ?? null, parent, idHint: frame.idHint });
182981
183369
  parent = key;
182982
183370
  }
182983
183371
  if (parent !== null) framed.set(manifest, parent);
@@ -183135,18 +183523,18 @@ function drawFolder(files, context, index2) {
183135
183523
  for (const entry2 of ordered) {
183136
183524
  const role = roleOf3(entry2);
183137
183525
  const frame = `frame:${entry2.folder}`;
183138
- groups.push({ key: frame, label: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "kustomization", sub: role, icon: "lucide:layers", idHint: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "root" });
183526
+ groups.push({ key: frame, label: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "kustomization", sub: role, ...role === "Overlay" ? { type: "environment" } : { otherwise: "resource-group" }, icon: "lucide:layers", idHint: entry2.folder.slice(common.length) || baseName2(entry2.folder) || "root" });
183139
183527
  nodes.push({ key: own2(entry2), type: "service", label: baseName2(entry2.folder) || "kustomization", sub: cleanText(entry2.details.length === 0 ? "kustomization.yaml" : entry2.details.slice(0, 4).join(", ")), icon: "lucide:layers", group: frame, idHint: `${baseName2(entry2.folder) || "kustomization"}_kustomization` });
183140
183528
  for (const resource of entry2.resources) {
183141
183529
  if (isRemote(resource)) {
183142
183530
  nodes.push({ key: `remote:${resource}`, type: "service", label: cleanText(resource.replace(/^https?:\/\//, "").replace(/\?.*$/, "").split("/").slice(-2).join("/")), sub: "Remote resource", icon: "lucide:globe", group: frame, idHint: "remote" });
183143
- edges.push({ from: own2(entry2), to: `remote:${resource}`, label: "builds on" });
183531
+ edges.push({ from: own2(entry2), to: `remote:${resource}`, label: "builds on", verb: "builds-on" });
183144
183532
  continue;
183145
183533
  }
183146
183534
  const path = resolvePath(entry2.folder, resource);
183147
183535
  const inner = path === null ? void 0 : byFolder.get(path);
183148
183536
  if (inner !== void 0) {
183149
- edges.push({ from: own2(entry2), to: own2(inner), label: "builds on" });
183537
+ edges.push({ from: own2(entry2), to: own2(inner), label: "builds on", verb: "builds-on" });
183150
183538
  continue;
183151
183539
  }
183152
183540
  const found = path === null ? void 0 : objects.get(path);
@@ -183160,7 +183548,7 @@ function drawFolder(files, context, index2) {
183160
183548
  }
183161
183549
  for (const component of entry2.components) {
183162
183550
  const inner = byFolder.get(resolvePath(entry2.folder, component) ?? "\0");
183163
- if (inner !== void 0) edges.push({ from: own2(entry2), to: own2(inner), label: "includes" });
183551
+ if (inner !== void 0) edges.push({ from: own2(entry2), to: own2(inner), label: "includes", verb: "includes" });
183164
183552
  else missing.push(`${entry2.folder === "" ? "" : `${entry2.folder}/`}${component}`);
183165
183553
  }
183166
183554
  for (const generator of entry2.generators) {
@@ -183175,16 +183563,16 @@ function drawFolder(files, context, index2) {
183175
183563
  const key = `patch:${entry2.folder}/${patch.path ?? `${kind ?? "any"}/${name2 ?? "any"}`}`;
183176
183564
  nodes.push({ key, type: "service", label: patch.path === null ? `${kind ?? "object"} ${name2 ?? ""}`.trim() : baseName2(patch.path), sub: cleanText(`Patch${kind === null ? "" : ` of ${kind}${name2 === null ? "" : ` ${name2}`}`}`), icon: "lucide:file-code", group: frame, idHint: `${baseName2(patch.path ?? "patch").replace(/\.ya?ml$/, "")}_patch` });
183177
183565
  const target = kind === null && name2 === null ? null : fileOf2(entry2, kind, name2) ?? [...objects].find(([, inside]) => inside.some((object2) => (kind === null || object2.kind === kind) && (name2 === null || object2.name === name2)))?.[0] ?? null;
183178
- if (target !== null) edges.push({ from: key, to: `file:${target}`, label: "patches" });
183566
+ if (target !== null) edges.push({ from: key, to: `file:${target}`, label: "patches", verb: "patches" });
183179
183567
  }
183180
183568
  }
183181
183569
  const drawnFiles = [...objects].filter(([path]) => nodes.some((node3) => node3.key === `file:${path}`));
183182
183570
  for (const [path, manifests] of drawnFiles) {
183183
183571
  for (const manifest of manifests) {
183184
183572
  for (const use of manifest.uses) {
183185
- for (const node3 of nodes) if (node3.key.startsWith("generator:") && node3.key.endsWith(`/${use.kind}/${use.name}`)) edges.push({ from: `file:${path}`, to: node3.key, arrow: "-->", label: "reads" });
183573
+ for (const node3 of nodes) if (node3.key.startsWith("generator:") && node3.key.endsWith(`/${use.kind}/${use.name}`)) edges.push({ from: `file:${path}`, to: node3.key, arrow: "-->", label: "reads", verb: use.kind === "Secret" ? "secret" : "reads-settings" });
183186
183574
  const holder = drawnFiles.find(([, others]) => others.some((other) => other.kind === use.kind && other.name === use.name));
183187
- if (holder !== void 0 && holder[0] !== path) edges.push({ from: `file:${path}`, to: `file:${holder[0]}`, arrow: "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads" });
183575
+ if (holder !== void 0 && holder[0] !== path) edges.push({ from: `file:${path}`, to: `file:${holder[0]}`, arrow: "-->", label: use.kind === "PersistentVolumeClaim" ? "mounts" : "reads", verb: use.kind === "PersistentVolumeClaim" ? "mounts" : use.kind === "Secret" ? "secret" : "reads-settings" });
183188
183576
  }
183189
183577
  for (const [other, inside] of drawnFiles) {
183190
183578
  if (other === path) continue;
@@ -184334,7 +184722,7 @@ function bundleArrows(nodes, groups, edges, ownBox, joinPairs = true) {
184334
184722
  if (sources.length < BUNDLE_AT) continue;
184335
184723
  if (sources.length < FEW && (!joinPairs || !HOSTS2.has(typeOf.get(frame) ?? "") || (pairs.get(frame) ?? 0) < 2 || sources.some((edge) => !edges.some((other) => !joinable.has(other) && (other.from === edge.from || other.to === edge.from))))) continue;
184336
184724
  for (const edge of sources) joined2.add(edge);
184337
- replacements.set(first, { from: own2.key, to: first.to, label: `${first.label ?? ""} (${sources.length})` });
184725
+ replacements.set(first, { from: own2.key, to: first.to, label: `${first.label ?? ""} (${sources.length})`, verb: first.verb ?? null });
184338
184726
  }
184339
184727
  const out = [];
184340
184728
  const written3 = /* @__PURE__ */ new Set();
@@ -184346,7 +184734,7 @@ function bundleArrows(nodes, groups, edges, ownBox, joinPairs = true) {
184346
184734
  if (written3.has(pair2)) {
184347
184735
  const index2 = out.findIndex((candidate2) => `${candidate2.from}\0${candidate2.to}` === pair2);
184348
184736
  const before = out[index2];
184349
- out[index2] = { ...before, label: [before.label, next.label].filter((label) => label !== null && label !== void 0 && label !== "").join(", ") };
184737
+ out[index2] = { ...before, label: [before.label, next.label].filter((label) => label !== null && label !== void 0 && label !== "").join(", "), verb: before.verb === next.verb ? before.verb ?? null : null };
184350
184738
  continue;
184351
184739
  }
184352
184740
  written3.add(pair2);
@@ -184869,6 +185257,10 @@ function stackArrows(plan) {
184869
185257
  }
184870
185258
  return { arrows, unjoined };
184871
185259
  }
185260
+ function verbOf(arrow) {
185261
+ if (arrow.grant || arrow.order !== true && POLICY_KEY.test(arrow.via)) return "grants";
185262
+ return LABEL_VERBS[arrow.label ?? ""] ?? null;
185263
+ }
184872
185264
  function joinArrows(arrows) {
184873
185265
  const joined2 = new Set(arrows.filter((arrow) => arrow.order !== true).flatMap((arrow) => [`${arrow.from}\0${arrow.to}`, `${arrow.to}\0${arrow.from}`]));
184874
185266
  const byPair = /* @__PURE__ */ new Map();
@@ -184880,10 +185272,12 @@ function joinArrows(arrows) {
184880
185272
  const grantWords = grants.length === 0 ? [] : grants.length === 1 ? [grants[0]?.label ?? "role"] : [`${grants.length} roles`];
184881
185273
  const words2 = [...grantWords, ...others];
184882
185274
  const label = words2.length === 0 ? null : words2.length <= MAX_LABELS2 ? words2.join(", ") : `${group2.length} links`;
184883
- return { from: first.from, to: first.to, label, ...group2.every((arrow) => arrow.order === true) ? { arrow: "-->" } : {} };
185275
+ const verbs = new Set(group2.map(verbOf));
185276
+ const verb = verbs.size === 1 ? [...verbs][0] ?? null : null;
185277
+ return { from: first.from, to: first.to, label, verb, ...group2.every((arrow) => arrow.order === true) ? { arrow: "-->" } : {} };
184884
185278
  });
184885
185279
  }
184886
- var ORDER_LABEL, INBOUND, QUIET, ATTRIBUTE_LABELS, TYPE_LABELS, VAGUE_ATTRIBUTES, MAX_LABELS2;
185280
+ var ORDER_LABEL, INBOUND, QUIET, ATTRIBUTE_LABELS, TYPE_LABELS, VAGUE_ATTRIBUTES, LABEL_VERBS, POLICY_KEY, MAX_LABELS2;
184887
185281
  var init_edges2 = __esm({
184888
185282
  "../../packages/diagrams/src/importers/terraform/edges.ts"() {
184889
185283
  "use strict";
@@ -184944,6 +185338,34 @@ var init_edges2 = __esm({
184944
185338
  service: "calls"
184945
185339
  };
184946
185340
  VAGUE_ATTRIBUTES = /* @__PURE__ */ new Set(["value", "values", "config", "configuration", "settings", "properties", "body", "content", "input", "inputs", "parameters", "source", "data"]);
185341
+ LABEL_VERBS = {
185342
+ identity: "grants",
185343
+ role: "grants",
185344
+ policy: "grants",
185345
+ grants: "grants",
185346
+ "grants on": "grants",
185347
+ principal: "grants",
185348
+ data: "stores",
185349
+ storage: "stores",
185350
+ cache: "stores",
185351
+ secret: "secret",
185352
+ secrets: "secret",
185353
+ password: "secret",
185354
+ messages: "sends",
185355
+ "dead letters": "sends",
185356
+ triggers: "triggers",
185357
+ logs: "reports",
185358
+ "logs to": "reports",
185359
+ telemetry: "reports",
185360
+ images: "pulls",
185361
+ "private link": "private",
185362
+ "VNet integration": "private",
185363
+ "public IP": "routes",
185364
+ firewall: "filters",
185365
+ calls: "calls",
185366
+ [ORDER_LABEL]: "depends-on"
185367
+ };
185368
+ POLICY_KEY = /(?:^|\.)[a-z0-9_]*polic(?:y|ies)[a-z0-9_]*\./;
184947
185369
  MAX_LABELS2 = 2;
184948
185370
  }
184949
185371
  });
@@ -185207,7 +185629,7 @@ function picture(plan, context, groupType) {
185207
185629
  if (shape === "container") {
185208
185630
  const frame = containerFrame(resource.key);
185209
185631
  const parent = byFile ? parentOf4(frame, resource) : plan.parents.get(frame) ?? null;
185210
- groups.push({ key: frame, label: box.label, type: null, icon: box.icon ?? null, sub: null, parent, idHint: `${resource.name}_group` });
185632
+ groups.push({ key: frame, label: box.label, type: HOLDER_TYPES[box.type] ?? "resource-group", icon: box.icon ?? null, sub: null, parent, idHint: `${resource.name}_group` });
185211
185633
  const product = role.role === "node" ? productOf2(resource, plan) : box.label;
185212
185634
  nodes.push({ ...box, label: box.label === product ? box.label : product, sub: box.label === product ? box.sub ?? null : withParts(literalDetails(resource), multiplicity(resource), resource.kind === "data" && !plan.compatible ? "existing" : null), group: frame });
185213
185635
  } else {
@@ -185227,7 +185649,7 @@ function picture(plan, context, groupType) {
185227
185649
  for (const [key, module] of plan.modules) {
185228
185650
  const source = module.source.replace(/^(?:\.\.?\/)+/, "");
185229
185651
  const label = `${humanize(module.name)}${module.instance === null ? "" : ` ${module.instance}`}`;
185230
- groups.push({ key, label, type: null, icon: moduleIcon(module.name, module.source, stackProvider(plan)) ?? "lucide:package", sub: cleanText(`${module.noun ?? "Module"} ${source}`), parent: plan.parents.get(key) ?? null, idHint: `${module.name}${module.instance === null ? "" : `_${module.instance}`}` });
185652
+ groups.push({ key, label, type: null, otherwise: "resource-group", icon: moduleIcon(module.name, module.source, stackProvider(plan)) ?? "lucide:package", sub: cleanText(`${module.noun ?? "Module"} ${source}`), parent: plan.parents.get(key) ?? null, idHint: `${module.name}${module.instance === null ? "" : `_${module.instance}`}` });
185231
185653
  }
185232
185654
  const holding = /* @__PURE__ */ new Set();
185233
185655
  for (const node3 of nodes) for (const key of [node3.group ?? "", ...plan.ancestors(node3.group ?? "")]) holding.add(key);
@@ -185269,7 +185691,7 @@ function withoutIdleBoxes(nodes, groups, edges) {
185269
185691
  const framed = groups.map((group2) => {
185270
185692
  const box = idle.get(group2.key);
185271
185693
  if (box === void 0) return group2;
185272
- const said = group2.label.includes(box.label) || group2.type !== null && group2.type !== void 0 ? null : box.label;
185694
+ const said = group2.label.includes(box.label) || group2.type !== null && group2.type !== void 0 && group2.key !== containerFrame(box.key) ? null : box.label;
185273
185695
  return { ...group2, sub: withParts(group2.sub ?? null, said, box.sub ?? null) };
185274
185696
  });
185275
185697
  return { nodes: kept2, groups: framed };
@@ -185293,7 +185715,7 @@ function literalDetails(resource) {
185293
185715
  const values = names8.map((name2) => literal2(resource, name2)).filter((value) => value !== null && value !== "");
185294
185716
  return values.length === 0 ? null : values.join(" ");
185295
185717
  }
185296
- var ACCOUNT_NAMES;
185718
+ var ACCOUNT_NAMES, HOLDER_TYPES;
185297
185719
  var init_picture = __esm({
185298
185720
  "../../packages/diagrams/src/importers/terraform/picture.ts"() {
185299
185721
  "use strict";
@@ -185306,6 +185728,17 @@ var init_picture = __esm({
185306
185728
  init_plan9();
185307
185729
  init_resources();
185308
185730
  ACCOUNT_NAMES = { aws: "AWS account", azure: "Azure subscription", gcp: "Google Cloud project" };
185731
+ HOLDER_TYPES = {
185732
+ database: "data-tier",
185733
+ cache: "data-tier",
185734
+ storage: "storage-account",
185735
+ queue: "messaging",
185736
+ secret: "security",
185737
+ identity: "identity-boundary",
185738
+ service: "app-tier",
185739
+ function: "app-tier",
185740
+ gateway: "edge"
185741
+ };
185309
185742
  }
185310
185743
  });
185311
185744
 
@@ -187362,7 +187795,7 @@ function chainOf(expr) {
187362
187795
  }
187363
187796
  return { base: current, steps };
187364
187797
  }
187365
- function nameOf7(slot, evaluator) {
187798
+ function nameOf8(slot, evaluator) {
187366
187799
  const named = bodyObject(slot.declaration.value)?.entries.find((entry2) => entry2.key === "name")?.value;
187367
187800
  return named === void 0 ? null : textOf5(evaluator.evaluate(named, slot.env));
187368
187801
  }
@@ -187377,15 +187810,15 @@ var init_slots2 = __esm({
187377
187810
 
187378
187811
  // ../../packages/diagrams/src/importers/bicep/items.ts
187379
187812
  function withoutReadCopies(items2) {
187380
- const nameOf10 = (item) => item.attributes.find((attribute2) => attribute2.name === "name")?.value ?? null;
187813
+ const nameOf11 = (item) => item.attributes.find((attribute2) => attribute2.name === "name")?.value ?? null;
187381
187814
  const made = /* @__PURE__ */ new Map();
187382
187815
  for (const item of items2) {
187383
- const name2 = nameOf10(item);
187816
+ const name2 = nameOf11(item);
187384
187817
  if (!item.existing && name2 !== null) made.set(`${item.type}\0${name2}`, item.name);
187385
187818
  }
187386
187819
  const alias = /* @__PURE__ */ new Map();
187387
187820
  for (const item of items2) {
187388
- const name2 = nameOf10(item);
187821
+ const name2 = nameOf11(item);
187389
187822
  const real = name2 === null ? void 0 : made.get(`${item.type}\0${name2}`);
187390
187823
  if (item.existing && real !== void 0) alias.set(item.name, real);
187391
187824
  }
@@ -187478,18 +187911,18 @@ var init_items3 = __esm({
187478
187911
  }
187479
187912
  /** The resource a child names as its parent by the start of its name (`vnet-shop` of `vnet-shop/app`), when the stack declares it. */
187480
187913
  parentByName(slot) {
187481
- const name2 = nameOf7(slot, this.evaluator);
187914
+ const name2 = nameOf8(slot, this.evaluator);
187482
187915
  const segments = slot.armType.split("/");
187483
187916
  if (name2 === null || !name2.includes("/") || segments.length < 3) return null;
187484
187917
  const parentType = segments.slice(0, -1).join("/").toLowerCase();
187485
187918
  const parentName = name2.slice(0, name2.lastIndexOf("/"));
187486
- for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === parentType && nameOf7(candidate2, this.evaluator) === parentName) return candidate2.transparent ? candidate2.holder?.name ?? null : candidate2.name;
187919
+ for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === parentType && nameOf8(candidate2, this.evaluator) === parentName) return candidate2.transparent ? candidate2.holder?.name ?? null : candidate2.name;
187487
187920
  if (/\/(?:blob|queue|table|file)services$/.test(parentType) && segments.length >= 4) return this.declared(segments.slice(0, -2).join("/"), parentName.slice(0, parentName.lastIndexOf("/")));
187488
187921
  return null;
187489
187922
  }
187490
187923
  /** The item of a type and a name the deployment declares, or null. */
187491
187924
  declared(type, name2) {
187492
- for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === type.toLowerCase() && nameOf7(candidate2, this.evaluator) === name2) return candidate2.name;
187925
+ for (const list of this.slots.values()) for (const candidate2 of list) if (candidate2.armType.toLowerCase() === type.toLowerCase() && nameOf8(candidate2, this.evaluator) === name2) return candidate2.name;
187493
187926
  return null;
187494
187927
  }
187495
187928
  /** The item that stands for a name: a storage service's account, since the service itself is not drawn. */
@@ -187730,9 +188163,9 @@ var init_references = __esm({
187730
188163
  if (type !== null && last !== null && last !== void 0) {
187731
188164
  for (const list of this.slots.values()) {
187732
188165
  for (const slot of list) {
187733
- const own2 = nameOf7(slot, this.evaluator);
188166
+ const own2 = nameOf8(slot, this.evaluator);
187734
188167
  if (slot.armType.toLowerCase() === type && (own2 === last || own2 === names8.join("/"))) return [slot.name];
187735
- if (type === `${slot.armType.toLowerCase()}/subnets` && nameOf7(slot, this.evaluator) === names8[names8.length - 2]) {
188168
+ if (type === `${slot.armType.toLowerCase()}/subnets` && nameOf8(slot, this.evaluator) === names8[names8.length - 2]) {
187736
188169
  const subnet = slot.subnets.find((entry2) => entry2.display === last || entry2.label === last);
187737
188170
  if (subnet !== void 0) return [subnet.name];
187738
188171
  }
@@ -188885,6 +189318,74 @@ var init_document2 = __esm({
188885
189318
  }
188886
189319
  });
188887
189320
 
189321
+ // ../../packages/diagrams/src/importers/asyncapi/layers.ts
189322
+ function adjacency(keys, arrows, reverse) {
189323
+ const next = new Map(keys.map((key) => [key, []]));
189324
+ for (const arrow of arrows) next.get(reverse ? arrow.to : arrow.from)?.push(reverse ? arrow.from : arrow.to);
189325
+ return next;
189326
+ }
189327
+ function finishOrder(start2, next, seen, finished2) {
189328
+ const stack = [{ key: start2, index: 0 }];
189329
+ seen.add(start2);
189330
+ while (stack.length > 0) {
189331
+ const top = stack[stack.length - 1];
189332
+ const following = next.get(top.key) ?? [];
189333
+ if (top.index < following.length) {
189334
+ const key = following[top.index];
189335
+ top.index += 1;
189336
+ if (!seen.has(key)) {
189337
+ seen.add(key);
189338
+ stack.push({ key, index: 0 });
189339
+ }
189340
+ } else {
189341
+ finished2.push(top.key);
189342
+ stack.pop();
189343
+ }
189344
+ }
189345
+ }
189346
+ function layersOf(keys, arrows) {
189347
+ const forward = adjacency(keys, arrows, false);
189348
+ const backward = adjacency(keys, arrows, true);
189349
+ const finished2 = [];
189350
+ const seen = /* @__PURE__ */ new Set();
189351
+ for (const key of keys) if (!seen.has(key)) finishOrder(key, forward, seen, finished2);
189352
+ const component = /* @__PURE__ */ new Map();
189353
+ const members2 = [];
189354
+ const assigned = /* @__PURE__ */ new Set();
189355
+ for (const key of [...finished2].reverse()) {
189356
+ if (assigned.has(key)) continue;
189357
+ const found = [];
189358
+ finishOrder(key, backward, assigned, found);
189359
+ for (const member of found) component.set(member, members2.length);
189360
+ members2.push(found);
189361
+ }
189362
+ const looped = new Set(members2.filter((group2) => group2.length > 1).flat());
189363
+ for (const arrow of arrows) if (arrow.from === arrow.to) looped.add(arrow.from);
189364
+ const step = new Array(members2.length).fill(0);
189365
+ const fed = /* @__PURE__ */ new Set();
189366
+ for (let index2 = 0; index2 < members2.length; index2 += 1) {
189367
+ for (const key of members2[index2] ?? []) {
189368
+ for (const to of forward.get(key) ?? []) {
189369
+ const target = component.get(to);
189370
+ if (target === index2) continue;
189371
+ step[target] = Math.max(step[target], step[index2] + 1);
189372
+ fed.add(target);
189373
+ }
189374
+ }
189375
+ }
189376
+ for (let index2 = members2.length - 1; index2 >= 0; index2 -= 1) {
189377
+ if (fed.has(index2)) continue;
189378
+ const targets = (members2[index2] ?? []).flatMap((key) => forward.get(key) ?? []).map((to) => component.get(to)).filter((target) => target !== index2);
189379
+ if (targets.length > 0) step[index2] = Math.min(...targets.map((target) => step[target])) - 1;
189380
+ }
189381
+ return { layer: new Map(keys.map((key) => [key, step[component.get(key)]])), looped };
189382
+ }
189383
+ var init_layers2 = __esm({
189384
+ "../../packages/diagrams/src/importers/asyncapi/layers.ts"() {
189385
+ "use strict";
189386
+ }
189387
+ });
189388
+
188888
189389
  // ../../packages/diagrams/src/importers/asyncapi/spec.ts
188889
189390
  function messageName(root, node3, fallback) {
188890
189391
  const ref = refOf(node3);
@@ -189019,6 +189520,7 @@ function readAsyncApi(text3, context) {
189019
189520
  const edges = [];
189020
189521
  const arrows = /* @__PURE__ */ new Map();
189021
189522
  const channelBox = /* @__PURE__ */ new Map();
189523
+ const channelFamily = /* @__PURE__ */ new Map();
189022
189524
  const brokerHosts = /* @__PURE__ */ new Map();
189023
189525
  const brokerServers = /* @__PURE__ */ new Map();
189024
189526
  let operations = 0;
@@ -189038,8 +189540,9 @@ function readAsyncApi(text3, context) {
189038
189540
  const key = `channel:${channel.address}`;
189039
189541
  const existing = channelBox.get(key);
189040
189542
  if (existing === void 0) {
189041
- const box = { key, type: "queue", label: cleanText(channel.address), sub: channel.description === null ? null : cleanText(channel.description), group: family === void 0 ? null : `broker:${family}`, idHint: channel.address };
189543
+ const box = { key, type: "queue", label: cleanText(channel.address), sub: channel.description === null ? null : cleanText(channel.description), group: null, idHint: channel.address };
189042
189544
  channelBox.set(key, box);
189545
+ if (family !== void 0) channelFamily.set(key, family);
189043
189546
  nodes.push(box);
189044
189547
  }
189045
189548
  }
@@ -189053,19 +189556,37 @@ function readAsyncApi(text3, context) {
189053
189556
  arrows.set(pair2, { from, to, messages: [...arrows.get(pair2)?.messages ?? [], ...operation.messages] });
189054
189557
  }
189055
189558
  }
189056
- for (const arrow of arrows.values()) edges.push({ from: arrow.from, to: arrow.to, label: messagesLabel(arrow.messages) });
189057
- const usedFamilies = new Set(nodes.map((node3) => node3.group).filter((group2) => group2 !== null && group2 !== void 0));
189559
+ for (const arrow of arrows.values()) edges.push({ from: arrow.from, to: arrow.to, label: messagesLabel(arrow.messages), verb: arrow.from.startsWith("channel:") ? "receives" : "sends" });
189560
+ const { layer: layer2, looped } = layersOf(
189561
+ nodes.map((node3) => node3.key),
189562
+ edges
189563
+ );
189564
+ const brokerIcon = (family) => BROKERS[family]?.icon ?? (brokerServers.get(family) ?? []).map((server) => iconForName(`${server.name} ${server.host ?? ""}`, "queue")).find((icon) => icon?.startsWith("simple:") === true) ?? null;
189565
+ const frameSteps = /* @__PURE__ */ new Map();
189566
+ const frameKey = (family, step) => {
189567
+ const index3 = (frameSteps.get(family) ?? []).indexOf(step);
189568
+ return index3 === 0 ? `broker:${family}` : `broker:${family}:${index3 + 1}`;
189569
+ };
189570
+ const placed = nodes.map((node3) => {
189571
+ const family = channelFamily.get(node3.key);
189572
+ if (family === void 0) return node3;
189573
+ if (looped.has(node3.key)) return { ...node3, icon: brokerIcon(family) };
189574
+ const step = layer2.get(node3.key) ?? 0;
189575
+ const steps = frameSteps.get(family) ?? [];
189576
+ if (!steps.includes(step)) frameSteps.set(family, [...steps, step]);
189577
+ return { ...node3, group: frameKey(family, step) };
189578
+ });
189579
+ const usedFamilies = new Set(channelFamily.values());
189058
189580
  for (const family of [...brokerServers.keys()]) {
189059
- const key = `broker:${family}`;
189060
- if (!usedFamilies.has(key)) continue;
189061
189581
  const broker = BROKERS[family];
189062
189582
  const hosts = [...brokerHosts.get(family) ?? []];
189063
- const servers = brokerServers.get(family) ?? [];
189064
- const named = servers.map((server) => iconForName(`${server.name} ${server.host ?? ""}`, "queue")).find((icon) => icon?.startsWith("simple:") === true) ?? null;
189065
189583
  const where = hosts.length === 0 ? null : hosts.length === 1 ? hosts[0] ?? null : `${hosts.length} hosts`;
189066
- groups.push({ key, label: broker?.label ?? `${family.toUpperCase()} broker`, type: "messaging", icon: broker?.icon ?? named, sub: where === null ? null : cleanText(where), idHint: family });
189584
+ const steps = [...frameSteps.get(family) ?? []].sort((a, b) => a - b);
189585
+ for (const step of steps) {
189586
+ groups.push({ key: frameKey(family, step), label: broker?.label ?? `${family.toUpperCase()} broker`, type: "messaging", icon: brokerIcon(family), sub: where === null || step !== steps[0] ? null : cleanText(where), idHint: family });
189587
+ }
189067
189588
  }
189068
- const unused = [...brokerServers.entries()].filter(([family]) => !usedFamilies.has(`broker:${family}`)).flatMap(([, servers]) => servers);
189589
+ const unused = [...brokerServers.entries()].filter(([family]) => !usedFamilies.has(family)).flatMap(([, servers]) => servers);
189069
189590
  if (unused.length > 0) {
189070
189591
  const first = unused[0];
189071
189592
  const file2 = applications.find(({ app }) => app.servers.includes(first))?.file ?? files[0];
@@ -189074,14 +189595,14 @@ function readAsyncApi(text3, context) {
189074
189595
  const sends = (key) => edges.some((edge) => edge.from === key);
189075
189596
  const receives = (key) => edges.some((edge) => edge.to === key);
189076
189597
  const rank = (node3) => node3.type === "queue" ? 1 : sends(node3.key) && !receives(node3.key) ? 0 : !sends(node3.key) && receives(node3.key) ? 2 : 1;
189077
- const ordered = [...nodes].sort((a, b) => rank(a) - rank(b));
189598
+ const ordered = [...placed].sort((a, b) => rank(a) - rank(b));
189078
189599
  const one = applications.length === 1 ? applications[0]?.app : void 0;
189079
189600
  const channels = channelBox.size;
189080
189601
  return writeFlow(
189081
189602
  {
189082
189603
  title: context.title ?? (one === void 0 ? "Event flows" : cleanText(one.title)),
189083
189604
  desc: one === void 0 ? `${countOf2(applications.length, "application")} and the ${countOf2(channels, "channel")} where they meet: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI.` : `What ${cleanText(one.title)} sends and receives on ${countOf2(channels, "channel")}: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI ${cleanText(one.version)}.`,
189084
- direction: "right",
189605
+ direction: "down",
189085
189606
  nodes: ordered,
189086
189607
  groups,
189087
189608
  edges,
@@ -189099,6 +189620,7 @@ var init_asyncapi = __esm({
189099
189620
  init_core();
189100
189621
  init_shared2();
189101
189622
  init_document2();
189623
+ init_layers2();
189102
189624
  init_spec();
189103
189625
  BROKERS = {
189104
189626
  kafka: { label: "Kafka", icon: "simple:apachekafka" },
@@ -189460,12 +189982,12 @@ function readBackstage(textIn, context) {
189460
189982
  for (const group2 of entities.filter((entity) => entity.kind === "Group")) {
189461
189983
  const members2 = entities.filter((entity) => entity.kind === "User" && entity.memberOf.includes(group2.ref)).map((user) => user.title ?? user.name);
189462
189984
  const said = members2.length === 0 ? null : members2.length <= 2 ? members2.join(", ") : countOf2(members2.length, "member");
189463
- groups.push({ key: group2.ref, label: group2.title ?? group2.name, sub: cleanText(["Group", group2.type, said].filter((part) => part !== null).join(", ")), icon: "lucide:users", parent: group2.parent !== null && byRef.has(group2.parent) ? group2.parent : null, idHint: group2.name });
189985
+ groups.push({ key: group2.ref, label: group2.title ?? group2.name, sub: cleanText(["Group", group2.type, said].filter((part) => part !== null).join(", ")), type: "identity-boundary", icon: "lucide:users", parent: group2.parent !== null && byRef.has(group2.parent) ? group2.parent : null, idHint: group2.name });
189464
189986
  }
189465
189987
  for (const entity of boxes2) if (entity.owner !== null && byRef.get(entity.owner)?.kind === "Group") frameOf3.set(entity.ref, entity.owner);
189466
189988
  } else {
189467
- for (const domain2 of entities.filter((entity) => entity.kind === "Domain")) groups.push({ key: domain2.ref, label: domain2.title ?? domain2.name, sub: cleanText(`Domain${domain2.owner === null ? "" : `, owned by ${nameOfRef2(domain2.owner)}`}`), icon: "lucide:map", idHint: domain2.name });
189468
- for (const system of entities.filter((entity) => entity.kind === "System")) groups.push({ key: system.ref, label: system.title ?? system.name, sub: cleanText(`System${system.owner === null ? "" : `, owned by ${nameOfRef2(system.owner)}`}`), icon: "lucide:boxes", parent: system.domain !== null && byRef.has(system.domain) ? system.domain : null, idHint: system.name });
189989
+ for (const domain2 of entities.filter((entity) => entity.kind === "Domain")) groups.push({ key: domain2.ref, label: domain2.title ?? domain2.name, sub: cleanText(`Domain${domain2.owner === null ? "" : `, owned by ${nameOfRef2(domain2.owner)}`}`), type: "resource-group", tone: "blue", icon: "lucide:map", idHint: domain2.name });
189990
+ for (const system of entities.filter((entity) => entity.kind === "System")) groups.push({ key: system.ref, label: system.title ?? system.name, sub: cleanText(`System${system.owner === null ? "" : `, owned by ${nameOfRef2(system.owner)}`}`), type: "app-tier", icon: "lucide:boxes", parent: system.domain !== null && byRef.has(system.domain) ? system.domain : null, idHint: system.name });
189469
189991
  for (const entity of boxes2) if (entity.system !== null && byRef.get(entity.system)?.kind === "System") frameOf3.set(entity.ref, entity.system);
189470
189992
  }
189471
189993
  const order = { Component: 0, API: 1, Resource: 2 };
@@ -189478,19 +190000,19 @@ function readBackstage(textIn, context) {
189478
190000
  const drawn = new Set(nodes.map((node3) => node3.key));
189479
190001
  const edges = [];
189480
190002
  const unknown2 = /* @__PURE__ */ new Set();
189481
- const link2 = (from, to, label, arrow) => {
190003
+ const link2 = (from, to, label, verb, arrow) => {
189482
190004
  if (!drawn.has(to)) {
189483
190005
  if (!byRef.has(to)) unknown2.add(to);
189484
190006
  return;
189485
190007
  }
189486
- edges.push({ from, to, label, ...arrow === void 0 ? {} : { arrow } });
190008
+ edges.push({ from, to, label, verb, ...arrow === void 0 ? {} : { arrow } });
189487
190009
  };
189488
190010
  for (const entity of ordered) {
189489
- for (const api of entity.providesApis) link2(entity.ref, api, "provides");
189490
- for (const api of entity.consumesApis) link2(entity.ref, api, "consumes");
189491
- for (const target of entity.dependsOn) link2(entity.ref, target, "depends on");
189492
- for (const source of entity.dependencyOf) if (drawn.has(source)) link2(source, entity.ref, "depends on");
189493
- if (entity.kind === "Component" && entity.parent !== null) link2(entity.ref, entity.parent, "part of", "-->");
190011
+ for (const api of entity.providesApis) link2(entity.ref, api, "provides", "provides");
190012
+ for (const api of entity.consumesApis) link2(entity.ref, api, "consumes", "consumes");
190013
+ for (const target of entity.dependsOn) link2(entity.ref, target, "depends on", "depends-on");
190014
+ for (const source of entity.dependencyOf) if (drawn.has(source)) link2(source, entity.ref, "depends on", "depends-on");
190015
+ if (entity.kind === "Component" && entity.parent !== null) link2(entity.ref, entity.parent, "part of", null, "-->");
189494
190016
  }
189495
190017
  if (unknown2.size > 0) {
189496
190018
  context.report.add(DIAGNOSTIC_CODES.importUnresolved, START, `${countOf2(unknown2.size, "relation")} name${unknown2.size === 1 ? "s an entity" : " entities"} the files do not describe, so no arrow is drawn: ${[...unknown2].slice(0, 3).join(", ")}.`, "Send the catalog-info.yaml files that describe them, or check that each reference names its kind, such as resource:orders-db.");
@@ -190700,7 +191222,7 @@ function edgesOf2(services, volumes) {
190700
191222
  for (const other of services) {
190701
191223
  if (other.name !== service.name && service.environment.some((value) => namesHost2(value, other.name))) edges.push({ from: `service:${service.name}`, to: `service:${other.name}` });
190702
191224
  }
190703
- for (const volume of service.volumes) if (volumes.has(volume)) edges.push({ from: `service:${service.name}`, to: `volume:${volume}`, label: "mounts" });
191225
+ for (const volume of service.volumes) if (volumes.has(volume)) edges.push({ from: `service:${service.name}`, to: `volume:${volume}`, label: "mounts", verb: "mounts" });
190704
191226
  }
190705
191227
  return edges;
190706
191228
  }
@@ -192416,12 +192938,12 @@ function helmFrames(manifests) {
192416
192938
  const release = releaseOf(manifest);
192417
192939
  const chart = chartLabel(manifest);
192418
192940
  const partOf2 = manifest.meta.get("app.kubernetes.io/part-of");
192419
- if (partOf2 !== void 0 && partOf2 !== "" && partOf2 !== release) frames.push({ key: `part-of:${partOf2}`, label: partOf2, sub: "Application", icon: "lucide:boxes", idHint: partOf2 });
192941
+ if (partOf2 !== void 0 && partOf2 !== "" && partOf2 !== release) frames.push({ key: `part-of:${partOf2}`, label: partOf2, sub: "Application", icon: "lucide:boxes", type: "app-tier", idHint: partOf2 });
192420
192942
  if (release === null) return frames;
192421
192943
  const several = (charts.get(release)?.size ?? 0) > 1;
192422
192944
  const only = several || chart === null ? null : chart;
192423
- frames.push({ key: `release:${release}`, label: release, sub: cleanText(only === null ? "Release" : `Release of ${only.name}${only.version === null ? "" : ` ${only.version}`}`), icon: "simple:helm", idHint: release });
192424
- if (several && chart !== null) frames.push({ key: `chart:${release}/${chart.name}`, label: chart.name, sub: cleanText(`Chart${chart.version === null ? "" : ` ${chart.version}`}`), icon: "lucide:package", idHint: chart.name });
192945
+ frames.push({ key: `release:${release}`, label: release, sub: cleanText(only === null ? "Release" : `Release of ${only.name}${only.version === null ? "" : ` ${only.version}`}`), icon: "simple:helm", type: "resource-group", tone: "blue", idHint: release });
192946
+ if (several && chart !== null) frames.push({ key: `chart:${release}/${chart.name}`, label: chart.name, sub: cleanText(`Chart${chart.version === null ? "" : ` ${chart.version}`}`), icon: "lucide:package", type: "resource-group", idHint: chart.name });
192425
192947
  return frames;
192426
192948
  };
192427
192949
  }
@@ -192635,7 +193157,7 @@ function stepBoxes(jobs) {
192635
193157
  last.set(job.id, keys[keys.length - 1]);
192636
193158
  }
192637
193159
  for (const job of jobs) for (const need of job.needs) edges.push({ from: last.get(need) ?? "", to: first.get(job.id) ?? "" });
192638
- const groups = jobs.map((job) => ({ key: `job:${job.id}`, label: job.label, sub: job.detail, idHint: `job_${job.id}` }));
193160
+ const groups = jobs.map((job) => ({ key: `job:${job.id}`, label: job.label, sub: job.detail, otherwise: "ci-cd", idHint: `job_${job.id}` }));
192639
193161
  return { nodes, edges, groups };
192640
193162
  }
192641
193163
  function readWorkflow(source, context) {
@@ -192846,7 +193368,7 @@ function readGitlabCi(source, context) {
192846
193368
  nodes.push({ key: `job:${job.name}`, type: "step", label: cleanText(job.name), sub: subOf3(job), icon: job.image === null ? null : imageLook(job.image)?.icon ?? null, group: `stage:${stage}`, idHint: job.name });
192847
193369
  if (job.trigger !== null) {
192848
193370
  nodes.push({ key: `trigger:${job.name}`, type: "external", label: cleanText(job.trigger.label), sub: job.trigger.child ? "Child pipeline" : "Downstream pipeline", icon: "simple:gitlab", idHint: `${job.name}_pipeline` });
192849
- edges.push({ from: `job:${job.name}`, to: `trigger:${job.name}`, label: "triggers" });
193371
+ edges.push({ from: `job:${job.name}`, to: `trigger:${job.name}`, label: "triggers", verb: "triggers" });
192850
193372
  }
192851
193373
  }
192852
193374
  }
@@ -194308,7 +194830,7 @@ function stateResources(state, offsetOf) {
194308
194830
  const byUrn = new Map(all.map((resource) => [resource.urn, resource]));
194309
194831
  const counts = /* @__PURE__ */ new Map();
194310
194832
  for (const resource of all) counts.set(logicalName(resource.urn), (counts.get(logicalName(resource.urn)) ?? 0) + 1);
194311
- const nameOf10 = (urn) => (counts.get(logicalName(urn)) ?? 0) > 1 ? urn : logicalName(urn);
194833
+ const nameOf11 = (urn) => (counts.get(logicalName(urn)) ?? 0) > 1 ? urn : logicalName(urn);
194312
194834
  const safe3 = (name2) => name2.replace(/[^A-Za-z0-9_-]+/g, "_");
194313
194835
  const scopeOf2 = (resource) => {
194314
194836
  const chain2 = [];
@@ -194333,21 +194855,21 @@ function stateResources(state, offsetOf) {
194333
194855
  const outputs = target?.outputs;
194334
194856
  const name2 = typeof outputs === "object" && outputs !== null && !Array.isArray(outputs) ? outputs["name"] : null;
194335
194857
  const path = pathTo(inputs[property] ?? null, [target?.id ?? "", typeof name2 === "string" && property.endsWith("Name") ? name2 : ""], [property]);
194336
- return { path: path ?? [property], refs: [nameOf10(urn)] };
194858
+ return { path: path ?? [property], refs: [nameOf11(urn)] };
194337
194859
  })
194338
194860
  );
194339
194861
  const parent = resource.parent === null ? void 0 : byUrn.get(resource.parent);
194340
194862
  for (const urns of Object.values(resource.propertyDependencies)) for (const urn of urns) named.add(urn);
194341
- const dependsOn = resource.dependencies.filter((urn) => !named.has(urn) && byUrn.get(urn)?.custom === true && urn !== resource.urn && urn !== resource.parent).map(nameOf10);
194863
+ const dependsOn = resource.dependencies.filter((urn) => !named.has(urn) && byUrn.get(urn)?.custom === true && urn !== resource.urn && urn !== resource.parent).map(nameOf11);
194342
194864
  const properties = mergeValues2(resource.outputs, resource.inputs);
194343
194865
  return {
194344
- name: nameOf10(resource.urn),
194866
+ name: nameOf11(resource.urn),
194345
194867
  logical: logicalName(resource.urn),
194346
194868
  token: resource.type,
194347
194869
  properties,
194348
194870
  refs: refs.filter((entry2) => entry2.refs.length > 0),
194349
194871
  dependsOn,
194350
- parent: parent?.custom === true ? nameOf10(parent.urn) : null,
194872
+ parent: parent?.custom === true ? nameOf11(parent.urn) : null,
194351
194873
  existing: false,
194352
194874
  start: offsetOf(resource.urn),
194353
194875
  scope: scopeOf2(resource)
@@ -195591,7 +196113,7 @@ function writeC4(workspace, view, shown, title, desc) {
195591
196113
  };
195592
196114
  const writeBoundary = (boundary, depth) => {
195593
196115
  lines.gap();
195594
- lines.add(`boundary ${ids.idFor(`b:${boundary.key}`)} ${quote(cleanText(boundary.name))}${options2({ kind: boundary.kind, tech: boundary.tech === null ? null : cleanText(boundary.tech) }, ["tech"])} {`, depth);
196116
+ lines.add(`boundary ${ids.idFor(`b:${boundary.key}`)} ${quote(cleanText(boundary.name))}${options2({ kind: boundary.kind, tech: boundary.tech === null ? null : cleanText(boundary.tech), icon: BOUNDARY_LOOKS2[boundary.kind].icon, tone: BOUNDARY_LOOKS2[boundary.kind].tone }, ["tech"])} {`, depth);
195595
196117
  for (const entry2 of shown.elements) if (entry2.boundary === boundary.key) elementLine2(entry2, depth + 1);
195596
196118
  for (const child of shown.boundaries) if (child.parent === boundary.key) writeBoundary(child, depth + 1);
195597
196119
  lines.add("}", depth);
@@ -195682,7 +196204,7 @@ function readStructurizr(text3, context) {
195682
196204
  ids: written3.ids
195683
196205
  };
195684
196206
  }
195685
- var DATABASE_WORDS, LEVELS, LEVEL_WORDS, structurizrImporter;
196207
+ var BOUNDARY_LOOKS2, DATABASE_WORDS, LEVELS, LEVEL_WORDS, structurizrImporter;
195686
196208
  var init_structurizr = __esm({
195687
196209
  "../../packages/diagrams/src/importers/structurizr/index.ts"() {
195688
196210
  "use strict";
@@ -195692,6 +196214,12 @@ var init_structurizr = __esm({
195692
196214
  init_dsl();
195693
196215
  init_json2();
195694
196216
  init_view();
196217
+ BOUNDARY_LOOKS2 = {
196218
+ system: { icon: "lucide:blocks", tone: "blue" },
196219
+ container: { icon: "lucide:box", tone: "blue" },
196220
+ group: { icon: "lucide:boxes", tone: "violet" },
196221
+ enterprise: { icon: "lucide:building", tone: "neutral" }
196222
+ };
195695
196223
  DATABASE_WORDS = /\b(?:database|postgres(?:ql)?|mysql|mariadb|oracle|sql server|sqlite|mongodb|dynamodb|cassandra|cosmos ?db|couchdb|neo4j|elasticsearch|opensearch|clickhouse|snowflake|bigquery|redshift)\b/i;
195696
196224
  LEVELS = ["context", "container", "component"];
195697
196225
  LEVEL_WORDS = {
@@ -196328,7 +196856,7 @@ function childOf(parent, name2, line3) {
196328
196856
  parent.push(entry2);
196329
196857
  return entry2;
196330
196858
  }
196331
- function nameOf8(raw) {
196859
+ function nameOf9(raw) {
196332
196860
  const text3 = cleanText(raw, "");
196333
196861
  const isDir = text3.endsWith("/") && text3.length > 1;
196334
196862
  return { name: isDir ? text3.slice(0, -1) : text3, isDir };
@@ -196355,7 +196883,7 @@ function readCommand(lines) {
196355
196883
  depth += 1;
196356
196884
  at3 += 4;
196357
196885
  }
196358
- const { name: name2, isDir } = nameOf8(line3.slice(at3));
196886
+ const { name: name2, isDir } = nameOf9(line3.slice(at3));
196359
196887
  if (name2 === "") continue;
196360
196888
  if (!branched && root === null && top.length === 0) {
196361
196889
  root = name2;
@@ -196405,7 +196933,7 @@ function readOutline(lines) {
196405
196933
  for (const [index2, raw] of lines.entries()) {
196406
196934
  if (raw.trim() === "") continue;
196407
196935
  const { width, text: text3 } = indentOf6(raw);
196408
- const { name: name2, isDir } = nameOf8(text3);
196936
+ const { name: name2, isDir } = nameOf9(text3);
196409
196937
  if (name2 === "") continue;
196410
196938
  while (stack.length > 0 && (stack[stack.length - 1]?.width ?? 0) >= width) stack.pop();
196411
196939
  const parent = stack[stack.length - 1]?.entry;
@@ -198165,14 +198693,14 @@ function isProduct(ref) {
198165
198693
  return cloudSetOf2(ref) !== void 0 || setOf(ref) === "simple";
198166
198694
  }
198167
198695
  function isTheIcon(query, ref) {
198168
- if (normalizePhrase(nameOf9(ref)) === query) return true;
198696
+ if (normalizePhrase(nameOf10(ref)) === query) return true;
198169
198697
  if (customIconPhrases(ref)?.has(query) === true) return true;
198170
198698
  const set2 = cloudSetOf2(ref);
198171
- if (set2 !== void 0 && nameOf9(ref) === "cloud" && CLOUD_PROVIDER_SPECS[set2].names.includes(query)) return true;
198699
+ if (set2 !== void 0 && nameOf10(ref) === "cloud" && CLOUD_PROVIDER_SPECS[set2].names.includes(query)) return true;
198172
198700
  return (phrasesByText().get(query) ?? []).some((phrase2) => phrase2.ref === ref && !phrase2.isGeneric);
198173
198701
  }
198174
198702
  function nameHasWords(query, ref) {
198175
- const words2 = nameOf9(ref).split("-");
198703
+ const words2 = nameOf10(ref).split("-");
198176
198704
  return query.split(" ").every((word2) => words2.includes(word2));
198177
198705
  }
198178
198706
  function isContested(query, top, others, needsSameMatch) {
@@ -198188,7 +198716,7 @@ function classifyIcon(name2, results) {
198188
198716
  if (nameHasWords(query, top) && !isContested(query, top, others, false)) return "strong";
198189
198717
  return "ambiguous";
198190
198718
  }
198191
- var phraseIndex, setOf, nameOf9, cloudSetOf2;
198719
+ var phraseIndex, setOf, nameOf10, cloudSetOf2;
198192
198720
  var init_icon_confidence = __esm({
198193
198721
  "../../packages/diagrams/src/tools/workflows/icon-confidence.ts"() {
198194
198722
  "use strict";
@@ -198196,7 +198724,7 @@ var init_icon_confidence = __esm({
198196
198724
  init_icons();
198197
198725
  phraseIndex = null;
198198
198726
  setOf = (ref) => ref.slice(0, ref.indexOf(":"));
198199
- nameOf9 = (ref) => ref.slice(ref.indexOf(":") + 1);
198727
+ nameOf10 = (ref) => ref.slice(ref.indexOf(":") + 1);
198200
198728
  cloudSetOf2 = (ref) => CLOUD_SETS.find((set2) => set2 === setOf(ref));
198201
198729
  }
198202
198730
  });
@@ -198547,7 +199075,7 @@ function isMarkCell(cell2) {
198547
199075
  const word2 = cell2.trim().toLowerCase();
198548
199076
  return YES_WORDS.includes(word2) || NO_WORDS.includes(word2) || TICKS.includes(word2) || CROSSES2.includes(word2) || word2 === "partial";
198549
199077
  }
198550
- function wordsOf5(text3) {
199078
+ function wordsOf6(text3) {
198551
199079
  return text3.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter((word2) => word2 !== "");
198552
199080
  }
198553
199081
  function cleanLabel2(text3) {
@@ -198581,7 +199109,7 @@ function idMaker(fallback) {
198581
199109
  let count3 = 0;
198582
199110
  return (label) => {
198583
199111
  count3 += 1;
198584
- const words2 = wordsOf5(label).slice(0, 3);
199112
+ const words2 = wordsOf6(label).slice(0, 3);
198585
199113
  let id = words2.join("_");
198586
199114
  if (!/^[a-z]/.test(id) || RESERVED_IDS.has(id)) id = `${fallback}${count3}`;
198587
199115
  id = id.slice(0, 24);
@@ -199319,7 +199847,7 @@ function singular2(word2) {
199319
199847
  function iconFor(name2) {
199320
199848
  const product = iconNamedBy(name2, null);
199321
199849
  if (product !== null) return product;
199322
- for (const word2 of wordsOf5(name2)) {
199850
+ for (const word2 of wordsOf6(name2)) {
199323
199851
  const ref = ROLE_ICONS2[word2] ?? ROLE_ICONS2[singular2(word2)];
199324
199852
  if (ref !== void 0 && isKnownIcon(ref)) return ref;
199325
199853
  }
@@ -199411,7 +199939,7 @@ var init_icons4 = __esm({
199411
199939
  function chain(ids, arrow = "->", label = "") {
199412
199940
  return ids.slice(1).map((id, index2) => `${ids[index2] ?? id} ${arrow} ${id}${label}`);
199413
199941
  }
199414
- function layersOf(material) {
199942
+ function layersOf2(material) {
199415
199943
  const lists = listsOf(material);
199416
199944
  if (lists.length >= 2) return lists.slice(0, 6).map((list) => ({ name: list.label, blocks: list.values.slice(0, 6) }));
199417
199945
  if (material.items.some((item) => item.depth > 0)) {
@@ -199433,6 +199961,11 @@ function partType(name2) {
199433
199961
  if (/\b(web app|browser|mobile|app|client|frontend)\b/i.test(name2)) return "client";
199434
199962
  return "service";
199435
199963
  }
199964
+ function roleOption(verb) {
199965
+ const named = verbNamedBy(verb);
199966
+ const role = named === null ? null : EDGE_STYLES[named].role;
199967
+ return role === null ? "" : ` role=${role}`;
199968
+ }
199436
199969
  function linkedFlow(material, title) {
199437
199970
  const ids = idMaker("part");
199438
199971
  const nodes = /* @__PURE__ */ new Map();
@@ -199446,7 +199979,7 @@ function linkedFlow(material, title) {
199446
199979
  return node3.id;
199447
199980
  };
199448
199981
  const links = material.relations.slice(0, MOST * 2).map((relation2) => ({ from: nodeOf5(relation2.from), to: nodeOf5(relation2.to), verb: relation2.verb }));
199449
- const edges = links.map((link2) => `${link2.from} -> ${link2.to} : ${quoted4(link2.verb)}`);
199982
+ const edges = links.map((link2) => `${link2.from} -> ${link2.to} : ${quoted4(link2.verb)}${roleOption(link2.verb)}`);
199450
199983
  return sourceOf(`flow ${quoted4(title)} direction=${directionFor(longestChain2(links))}`, [
199451
199984
  ...[...nodes.values()].map((node3) => `node ${node3.id} ${partType(node3.label)} ${quoted4(node3.label)}${iconOption(node3.label)}`),
199452
199985
  "",
@@ -199482,6 +200015,8 @@ var WIDEST_CHAIN, SYSTEM_BUILDERS;
199482
200015
  var init_systems = __esm({
199483
200016
  "../../packages/diagrams/src/guidance/starters/systems.ts"() {
199484
200017
  "use strict";
200018
+ init_constants();
200019
+ init_label_icon();
199485
200020
  init_icons4();
199486
200021
  init_text6();
199487
200022
  init_shared4();
@@ -199568,7 +200103,7 @@ var init_systems = __esm({
199568
200103
  },
199569
200104
  layers: (material, title) => {
199570
200105
  const ids = idMaker("block");
199571
- const body6 = layersOf(material).flatMap((layer2) => {
200106
+ const body6 = layersOf2(material).flatMap((layer2) => {
199572
200107
  const blocks2 = distinctLabels(layer2.blocks);
199573
200108
  return [`layer ${quoted4(layer2.name)} {`, ...(blocks2.length > 0 ? blocks2 : [layer2.name]).map((block) => ` block ${ids(block)} ${quoted4(block)}`), "}", ""];
199574
200109
  });
@@ -199622,13 +200157,13 @@ function stem(word2) {
199622
200157
  return word2;
199623
200158
  }
199624
200159
  function keyOf4(text3) {
199625
- return wordsOf5(text3).map(stem).join(" ");
200160
+ return wordsOf6(text3).map(stem).join(" ");
199626
200161
  }
199627
200162
  function wordPoints(texts) {
199628
200163
  const strong = /* @__PURE__ */ new Map();
199629
200164
  const weak = /* @__PURE__ */ new Map();
199630
200165
  for (const { text: text3, weight } of texts) {
199631
- const words2 = wordsOf5(text3).map(stem);
200166
+ const words2 = wordsOf6(text3).map(stem);
199632
200167
  const seen = /* @__PURE__ */ new Set();
199633
200168
  for (let start2 = 0; start2 < words2.length; start2 += 1) {
199634
200169
  let key = "";
@@ -199751,8 +200286,8 @@ var init_rank = __esm({
199751
200286
  MEDIUM = 4;
199752
200287
  INDEX = /* @__PURE__ */ new Map();
199753
200288
  for (const intent of INTENTS) {
199754
- for (const [phrases, isStrong] of [[intent.strong, true], [intent.weak, false]]) {
199755
- for (const phrase2 of phrases) INDEX.set(keyOf4(phrase2), [...INDEX.get(keyOf4(phrase2)) ?? [], { id: intent.id, isStrong }]);
200289
+ for (const [phrases2, isStrong] of [[intent.strong, true], [intent.weak, false]]) {
200290
+ for (const phrase2 of phrases2) INDEX.set(keyOf4(phrase2), [...INDEX.get(keyOf4(phrase2)) ?? [], { id: intent.id, isStrong }]);
199756
200291
  }
199757
200292
  }
199758
200293
  LONGEST_PHRASE = Math.max(...[...INDEX.keys()].map((key) => key.split(" ").length));
@@ -200854,6 +201389,7 @@ var init_server_info = __esm({
200854
201389
  "Set embedSource true on draw_diagram whenever you save an SVG into a repository, so the file carries its source and read_diagram can give it back next time; the source is then readable by anyone who has the file.",
200855
201390
  ...SAVING_INSTRUCTIONS,
200856
201391
  'An item with no icon= gets the icon its label names when the match is certain, such as the Postgres mark on a node labelled "Postgres", so label items with the product they are; icon=none keeps one item plain and icons=off on the header keeps them all plain.',
201392
+ 'For a finished look, label flow arrows with verbs ("reads", "publishes", "deploys to") so they draw icons, add role= (data, async, identity, private) to the lines that carry those, and give groups a type=; style in get_diagram_syntax says more.',
200857
201393
  "Use map_icons to choose icons for many names at once; it never guesses, so choose from the candidates for an ambiguous name, and never invent an icon.",
200858
201394
  "To move Mermaid to Plotwork, call convert_mermaid with one diagram, read every diagnostic and its hint, then choose node types and icons; for a whole Markdown file call sync_markdown with the document, its path, and convertMermaid true, write the returned markdown and files, then review the diff.",
200859
201395
  "When the repository has a plotwork.config.json (or .plotwork/config.json) at its root, read it and pass it as settings to every tool that takes settings: its defaults fill the header options the source leaves out, and broken style rules come back as warnings to fix.",