@likec4/language-server 1.9.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +3 -4
  4. package/dist/browser.d.mts +3 -4
  5. package/dist/browser.d.ts +3 -4
  6. package/dist/browser.mjs +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +1 -1
  12. package/dist/model-graph/index.cjs +1 -1
  13. package/dist/model-graph/index.mjs +1 -1
  14. package/dist/node.cjs +1 -1
  15. package/dist/node.d.cts +3 -4
  16. package/dist/node.d.mts +3 -4
  17. package/dist/node.d.ts +3 -4
  18. package/dist/node.mjs +1 -1
  19. package/dist/shared/{language-server.Q-wtPShM.mjs → language-server.BFBeyvV8.mjs} +486 -108
  20. package/dist/shared/{language-server.86lmJ8ZN.d.cts → language-server.BGy3FJPJ.d.cts} +43 -14
  21. package/dist/shared/{language-server.B1TZgyoH.cjs → language-server.Bfc-5M8A.cjs} +482 -104
  22. package/dist/shared/{language-server.CCB4ESN5.mjs → language-server.CbqwHp7Q.mjs} +184 -120
  23. package/dist/shared/{language-server.RjhrBZS0.d.ts → language-server.CnVuAxDh.d.ts} +43 -14
  24. package/dist/shared/{language-server.CFTY6j4e.d.mts → language-server.DEK39RmI.d.mts} +43 -14
  25. package/dist/shared/{language-server.D0bOlrCi.cjs → language-server.DJhoJBWh.cjs} +180 -116
  26. package/package.json +13 -11
  27. package/src/ast.ts +8 -6
  28. package/src/formatting/LikeC4Formatter.ts +390 -0
  29. package/src/formatting/utils.ts +26 -0
  30. package/src/generated/ast.ts +203 -11
  31. package/src/generated/grammar.ts +2 -2
  32. package/src/generated/module.ts +1 -1
  33. package/src/like-c4.langium +34 -7
  34. package/src/lsp/CompletionProvider.ts +1 -1
  35. package/src/lsp/DocumentLinkProvider.ts +27 -15
  36. package/src/lsp/SemanticTokenProvider.ts +1 -1
  37. package/src/lsp/index.ts +1 -1
  38. package/src/model/fqn-index.ts +0 -1
  39. package/src/model/model-builder.ts +43 -32
  40. package/src/model/model-parser.ts +43 -21
  41. package/src/model-graph/compute-view/compute.ts +111 -80
  42. package/src/model-graph/compute-view/predicates.ts +3 -5
  43. package/src/model-graph/dynamic-view/compute.ts +96 -60
  44. package/src/model-graph/utils/buildElementNotations.ts +1 -1
  45. package/src/model-graph/utils/uniqueTags.test.ts +42 -0
  46. package/src/model-graph/utils/uniqueTags.ts +19 -0
  47. package/src/module.ts +6 -9
  48. package/src/test/testServices.ts +27 -7
  49. package/src/validation/index.ts +2 -1
  50. package/src/validation/property-checks.ts +13 -1
  51. package/src/validation/specification.ts +3 -3
  52. package/src/view-utils/resolve-relative-paths.ts +14 -17
@@ -1,5 +1,5 @@
1
1
  /******************************************************************************
2
- * This file was generated by langium-cli 3.1.1.
2
+ * This file was generated by langium-cli 3.2.0.
3
3
  * DO NOT EDIT MANUALLY!
4
4
  ******************************************************************************/
5
5
 
@@ -162,7 +162,12 @@ RelationBody: '{'
162
162
  ;
163
163
 
164
164
  RelationProperty:
165
- RelationStringProperty | RelationStyleProperty | LinkProperty | MetadataProperty;
165
+ RelationStringProperty |
166
+ RelationNavigateToProperty |
167
+ RelationStyleProperty |
168
+ LinkProperty |
169
+ MetadataProperty
170
+ ;
166
171
 
167
172
  RelationStringProperty:
168
173
  key=('title' | 'technology' | 'description') ':'? value=String ';'?;
@@ -183,7 +188,7 @@ MetadataBody: '{'
183
188
  ;
184
189
 
185
190
  MetadataAttribute:
186
- key=IdTerminal value=String
191
+ key=IdTerminal ':'? value=String ';'?
187
192
  ;
188
193
 
189
194
  // Views -------------------------------------
@@ -215,6 +220,9 @@ ViewRef:
215
220
  ElementViewRef:
216
221
  view=[ElementView];
217
222
 
223
+ DynamicViewRef:
224
+ view=[DynamicView];
225
+
218
226
  ElementViewBody: '{'
219
227
  tags=Tags?
220
228
  props+=ViewProperty*
@@ -225,8 +233,11 @@ ElementViewBody: '{'
225
233
  DynamicViewBody: '{'
226
234
  tags=Tags?
227
235
  props+=ViewProperty*
228
- (steps+=DynamicViewStep | rules+=DynamicViewRule)*
229
- '}'
236
+ (
237
+ steps+=(DynamicViewParallelSteps | DynamicViewStep) |
238
+ rules+=DynamicViewRule
239
+ )*
240
+ '}'
230
241
  ;
231
242
 
232
243
 
@@ -237,7 +248,8 @@ type StringProperty =
237
248
  MetadataAttribute |
238
249
  SpecificationElementStringProperty |
239
250
  SpecificationRelationshipStringProperty |
240
- NotationProperty
251
+ NotationProperty |
252
+ NotesProperty
241
253
  ;
242
254
 
243
255
  ViewProperty:
@@ -262,6 +274,12 @@ DynamicViewRule:
262
274
  ViewRuleAutoLayout
263
275
  ;
264
276
 
277
+ DynamicViewParallelSteps:
278
+ ('parallel'|'par') '{'
279
+ (steps+=DynamicViewStep)*
280
+ '}'
281
+ ;
282
+
265
283
  DynamicViewStep:
266
284
  source=ElementRef
267
285
  (isBackward?='<-' | '->' | '-[' kind=[RelationshipKind] ']->' | kind=[RelationshipKind:DotId] )
@@ -426,6 +444,9 @@ ViewRuleAutoLayout:
426
444
  NotationProperty:
427
445
  key='notation' ':'? value=String ';'?
428
446
  ;
447
+ NotesProperty:
448
+ key='notes' ':'? value=String ';'?
449
+ ;
429
450
 
430
451
  CustomElementProperties: '{'
431
452
  props+=(
@@ -439,8 +460,10 @@ CustomElementProperties: '{'
439
460
 
440
461
  CustomRelationProperties: '{'
441
462
  props+=(
463
+ RelationNavigateToProperty |
442
464
  RelationStringProperty |
443
465
  NotationProperty |
466
+ NotesProperty |
444
467
  RelationshipStyleProperty
445
468
  )*
446
469
  '}'
@@ -449,6 +472,9 @@ CustomRelationProperties: '{'
449
472
  NavigateToProperty:
450
473
  key='navigateTo' value=ViewRef;
451
474
 
475
+ RelationNavigateToProperty:
476
+ key='navigateTo' value=DynamicViewRef;
477
+
452
478
  // Common properties -------------------------------------
453
479
 
454
480
  LinkProperty:
@@ -565,7 +591,8 @@ fragment IsEqual:
565
591
  // Comments
566
592
  hidden terminal BLOCK_COMMENT: /\/\*[\s\S]*?\*\//;
567
593
  hidden terminal LINE_COMMENT: /\/\/[^\n\r]*/;
568
- hidden terminal WS: /\s+/;
594
+ hidden terminal WS: /[\t ]+/;
595
+ hidden terminal NL: /[\r\n]+/;
569
596
 
570
597
  // -----------------------------------
571
598
  // Terminals
@@ -593,4 +620,4 @@ terminal String: /"[^"]*"|'[^']*'/;
593
620
  // terminal TagId: HASH LETTER (LETTER | DIGIT | UNDERSCORE | DASH)*;
594
621
  // terminal IdTerminal: (LETTER | UNDERSCORE+ (LETTER | DIGIT)) (LETTER | DIGIT | UNDERSCORE | DASH)*;
595
622
  terminal IdTerminal: /[_]*[a-zA-Z][-\w]*/;
596
- terminal Hex: /[a-zA-Z0-9]+/;
623
+ terminal Hex: /[a-zA-Z0-9]+/;
@@ -1,7 +1,7 @@
1
1
  import { type CompletionProviderOptions, DefaultCompletionProvider } from 'langium/lsp'
2
2
 
3
3
  export class LikeC4CompletionProvider extends DefaultCompletionProvider {
4
- readonly completionOptions = {
4
+ override readonly completionOptions = {
5
5
  triggerCharacters: ['.']
6
6
  } satisfies CompletionProviderOptions
7
7
  }
@@ -1,10 +1,10 @@
1
1
  import type { LangiumDocument, MaybePromise } from 'langium'
2
2
  import { AstUtils, GrammarUtils } from 'langium'
3
3
  import type { DocumentLinkProvider } from 'langium/lsp'
4
- import { hasProtocol, isRelative, withBase } from 'ufo'
4
+ import { hasLeadingSlash, hasProtocol, isRelative, withoutBase, withoutLeadingSlash } from 'ufo'
5
5
  import type { DocumentLink, DocumentLinkParams } from 'vscode-languageserver'
6
6
  import { ast, isParsedLikeC4LangiumDocument } from '../ast'
7
- import { logError } from '../logger'
7
+ import { logWarnError } from '../logger'
8
8
  import type { LikeC4Services } from '../module'
9
9
 
10
10
  export class LikeC4DocumentLinkProvider implements DocumentLinkProvider {
@@ -20,34 +20,46 @@ export class LikeC4DocumentLinkProvider implements DocumentLinkProvider {
20
20
  }
21
21
  return AstUtils.streamAllContents(doc.parseResult.value)
22
22
  .filter(ast.isLinkProperty)
23
- .flatMap((n): DocumentLink | Iterable<DocumentLink> => {
23
+ .map((n): DocumentLink | null => {
24
24
  try {
25
25
  const range = GrammarUtils.findNodeForProperty(n.$cstNode, 'value')?.range
26
- if (!range) {
27
- return []
28
- }
29
26
  const target = this.resolveLink(doc, n.value)
30
- return {
31
- range,
32
- target
27
+ if (range && hasProtocol(target)) {
28
+ return {
29
+ range,
30
+ target
31
+ }
33
32
  }
34
33
  } catch (e) {
35
- logError(e)
36
- return []
34
+ logWarnError(e)
37
35
  }
36
+ return null
38
37
  })
38
+ .nonNullable()
39
39
  .toArray()
40
40
  }
41
41
 
42
42
  resolveLink(doc: LangiumDocument, link: string): string {
43
- if (hasProtocol(link)) {
43
+ if (hasProtocol(link) || hasLeadingSlash(link)) {
44
44
  return link
45
45
  }
46
+ const base = isRelative(link)
47
+ ? new URL(doc.uri.toString(true))
48
+ : this.services.shared.workspace.WorkspaceManager.workspaceURL
49
+ return new URL(link, base).toString()
50
+ }
51
+
52
+ relativeLink(doc: LangiumDocument, link: string): string | null {
53
+ if (hasLeadingSlash(link)) {
54
+ return withoutLeadingSlash(link)
55
+ }
46
56
  if (isRelative(link)) {
47
57
  const base = new URL(doc.uri.toString(true))
48
- return new URL(link, base).toString()
58
+ const linkURL = new URL(link, base).toString()
59
+ return withoutLeadingSlash(
60
+ withoutBase(linkURL, this.services.shared.workspace.WorkspaceManager.workspaceURL.toString())
61
+ )
49
62
  }
50
- const workspace = this.services.shared.workspace.WorkspaceManager.workspaceURL
51
- return withBase(link, workspace.toString())
63
+ return null
52
64
  }
53
65
  }
@@ -42,7 +42,7 @@ export class LikeC4SemanticTokenProvider extends AbstractSemanticTokenProvider {
42
42
  type: SemanticTokenTypes.function
43
43
  })
44
44
  }
45
- if (ast.isNavigateToProperty(node)) {
45
+ if (ast.isNavigateToProperty(node) || ast.isRelationNavigateToProperty(node)) {
46
46
  acceptor({
47
47
  node,
48
48
  property: 'key',
package/src/lsp/index.ts CHANGED
@@ -4,5 +4,5 @@ export * from './DocumentHighlightProvider'
4
4
  export * from './DocumentLinkProvider'
5
5
  export * from './DocumentSymbolProvider'
6
6
  export * from './HoverProvider'
7
- export * from './RenameProvider'
7
+ // export * from './RenameProvider'
8
8
  export * from './SemanticTokenProvider'
@@ -26,7 +26,6 @@ export class FqnIndex {
26
26
  services.shared.workspace.DocumentBuilder.onBuildPhase(
27
27
  DocumentState.IndexedContent,
28
28
  async (docs, _cancelToken) => {
29
- logger.debug(`[FqnIndex] onIndexedContent ${docs.length}:\n` + printDocs(docs))
30
29
  for (const doc of docs) {
31
30
  if (isLikeC4LangiumDocument(doc)) {
32
31
  delete doc.c4fqnIndex
@@ -1,24 +1,24 @@
1
+ import type * as c4 from '@likec4/core'
1
2
  import {
2
- compareByFqnHierarchically,
3
3
  compareRelations,
4
4
  computeColorValues,
5
5
  type CustomColorDefinitions,
6
6
  isElementView,
7
7
  isScopedElementView,
8
8
  parentFqn,
9
- type ScopedElementView,
9
+ sortByFqnHierarchically,
10
10
  type ViewID
11
11
  } from '@likec4/core'
12
- import type * as c4 from '@likec4/core'
13
12
  import { deepEqual as eq } from 'fast-equals'
14
13
  import type { Cancellation, LangiumDocument, LangiumDocuments, URI, WorkspaceCache } from 'langium'
15
14
  import { Disposable, DocumentState, interruptAndCheck } from 'langium'
16
15
  import {
17
16
  filter,
18
- find,
19
17
  flatMap,
20
18
  forEach,
19
+ groupBy,
21
20
  indexBy,
21
+ isEmpty,
22
22
  isNonNullish,
23
23
  isNullish,
24
24
  isNumber,
@@ -38,7 +38,8 @@ import type {
38
38
  ParsedAstRelation,
39
39
  ParsedAstSpecification,
40
40
  ParsedAstView,
41
- ParsedLikeC4LangiumDocument
41
+ ParsedLikeC4LangiumDocument,
42
+ ParsedLink
42
43
  } from '../ast'
43
44
  import { isParsedLikeC4LangiumDocument } from '../ast'
44
45
  import { logError, logger, logWarnError } from '../logger'
@@ -60,16 +61,24 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
60
61
  Object.assign(c4Specification.relationships, spec.relationships)
61
62
  Object.assign(c4Specification.colors, spec.colors)
62
63
  })
63
- const resolveLinks = (doc: LangiumDocument, links: c4.NonEmptyArray<c4.Link>) => {
64
- try {
65
- return links.map(l => ({
66
- ...l,
67
- url: services.lsp.DocumentLinkProvider.resolveLink(doc, l.url)
68
- })) as c4.NonEmptyArray<c4.Link>
69
- } catch (e) {
70
- logWarnError(e)
71
- return null
72
- }
64
+ function resolveLinks(doc: LangiumDocument, links: c4.NonEmptyArray<ParsedLink>) {
65
+ return map(
66
+ links,
67
+ (link): c4.Link => {
68
+ try {
69
+ const relative = services.lsp.DocumentLinkProvider.relativeLink(doc, link.url)
70
+ if (relative && relative !== link.url) {
71
+ return {
72
+ ...link,
73
+ relative
74
+ }
75
+ }
76
+ } catch (e) {
77
+ logWarnError(e)
78
+ }
79
+ return link
80
+ }
81
+ )
73
82
  }
74
83
 
75
84
  const customColorDefinitions: CustomColorDefinitions = mapValues(
@@ -77,7 +86,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
77
86
  c => computeColorValues(c.color)
78
87
  )
79
88
 
80
- const toModelElement = (doc: LangiumDocument) => {
89
+ function toModelElement(doc: LangiumDocument) {
81
90
  return ({
82
91
  tags,
83
92
  links: unresolvedLinks,
@@ -112,7 +121,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
112
121
  ...(color && { color }),
113
122
  ...(shape && { shape }),
114
123
  ...(icon && { icon }),
115
- ...(metadata && { metadata }),
124
+ ...(metadata && !isEmpty(metadata) && { metadata }),
116
125
  ...(__kind.notation && { notation: __kind.notation }),
117
126
  style: {
118
127
  ...(border && { border }),
@@ -138,7 +147,8 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
138
147
  flatMap(d => map(d.c4Elements, toModelElement(d))),
139
148
  filter(isTruthy),
140
149
  // sort from root elements to nested, so that parent is always present
141
- sort(compareByFqnHierarchically),
150
+ // Import to preserve the order from the source
151
+ sortByFqnHierarchically,
142
152
  reduce(
143
153
  (acc, el) => {
144
154
  const parent = parentFqn(el.id)
@@ -153,7 +163,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
153
163
  )
154
164
  )
155
165
 
156
- const toModelRelation = (doc: LangiumDocument) => {
166
+ function toModelRelation(doc: LangiumDocument) {
157
167
  return ({
158
168
  astPath,
159
169
  source,
@@ -180,7 +190,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
180
190
  target,
181
191
  kind,
182
192
  id
183
- }
193
+ } satisfies c4.Relation
184
194
  }
185
195
  return {
186
196
  ...(links && { links }),
@@ -188,7 +198,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
188
198
  source,
189
199
  target,
190
200
  id
191
- }
201
+ } satisfies c4.Relation
192
202
  }
193
203
  }
194
204
 
@@ -201,7 +211,7 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
201
211
  indexBy(prop('id'))
202
212
  )
203
213
 
204
- const toC4View = (doc: LangiumDocument) => {
214
+ function toC4View(doc: LangiumDocument) {
205
215
  const docUri = doc.uri.toString()
206
216
  return (parsedAstView: ParsedAstView): c4.LikeC4View => {
207
217
  let {
@@ -210,10 +220,8 @@ function buildModel(services: LikeC4Services, docs: ParsedLikeC4LangiumDocument[
210
220
  description,
211
221
  tags,
212
222
  links: unresolvedLinks,
213
-
214
223
  // ignore this property
215
224
  astPath: _ignore,
216
-
217
225
  // model should include discriminant __
218
226
  ...model
219
227
  } = parsedAstView
@@ -440,19 +448,22 @@ export class LikeC4ModelBuilder {
440
448
  }
441
449
  let computedView = result.view
442
450
 
443
- const allElementViews = values(model.views).filter(
444
- (v): v is ScopedElementView => isScopedElementView(v) && v.id !== viewId
451
+ const allElementViews = pipe(
452
+ model.views,
453
+ values(),
454
+ filter(isScopedElementView),
455
+ filter(v => v.id !== viewId),
456
+ groupBy(v => v.viewOf)
445
457
  )
446
458
 
447
- computedView.nodes.forEach(node => {
459
+ for (const node of computedView.nodes) {
448
460
  if (!node.navigateTo) {
449
- // find first element view that is not the current one
450
- const navigateTo = find(allElementViews, v => v.viewOf === node.id)
451
- if (navigateTo) {
452
- node.navigateTo = navigateTo.id
461
+ const viewsOfNode = allElementViews[node.id]
462
+ if (viewsOfNode) {
463
+ node.navigateTo = viewsOfNode[0].id
453
464
  }
454
465
  }
455
- })
466
+ }
456
467
 
457
468
  const previous = this.previousViews[viewId]
458
469
  computedView = previous && eq(computedView, previous) ? previous : computedView
@@ -2,7 +2,7 @@ import { type HexColorLiteral, invariant, isNonEmptyArray, nonexhaustive } from
2
2
  import type * as c4 from '@likec4/core'
3
3
  import type { AstNode, LangiumDocument } from 'langium'
4
4
  import { AstUtils, CstUtils } from 'langium'
5
- import { filter, flatMap, isDefined, isNonNullish, isTruthy, mapToObj, pipe } from 'remeda'
5
+ import { filter, first, flatMap, isDefined, isNonNullish, isTruthy, map, mapToObj, pipe } from 'remeda'
6
6
  import stripIndent from 'strip-indent'
7
7
  import type { Writable } from 'type-fest'
8
8
  import type {
@@ -247,6 +247,14 @@ export class LikeC4ModelParser {
247
247
  p => [p.key, p.value]
248
248
  )
249
249
 
250
+ const navigateTo = pipe(
251
+ astNode.body?.props ?? [],
252
+ filter(ast.isRelationNavigateToProperty),
253
+ map(p => p.value.view.ref?.name),
254
+ filter(isTruthy),
255
+ first()
256
+ )
257
+
250
258
  const title = removeIndent(astNode.title ?? bodyProps.title) ?? ''
251
259
  const description = removeIndent(bodyProps.description)
252
260
  const technology = removeIndent(astNode.technology) ?? toSingleLine(bodyProps.technology)
@@ -269,7 +277,8 @@ export class LikeC4ModelParser {
269
277
  ...(kind && { kind }),
270
278
  ...(tags && { tags }),
271
279
  ...(isNonEmptyArray(links) && { links }),
272
- ...toRelationshipStyleExcludeDefaults(styleProp?.props)
280
+ ...toRelationshipStyleExcludeDefaults(styleProp?.props),
281
+ ...(navigateTo && { navigateTo: navigateTo as c4.ViewID })
273
282
  }
274
283
  }
275
284
 
@@ -514,7 +523,7 @@ export class LikeC4ModelParser {
514
523
  const props = astNode.custom?.props ?? []
515
524
  return props.reduce(
516
525
  (acc, prop) => {
517
- if (ast.isRelationStringProperty(prop)) {
526
+ if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
518
527
  if (isDefined(prop.value)) {
519
528
  acc.customRelation[prop.key] = removeIndent(prop.value) ?? ''
520
529
  }
@@ -539,9 +548,10 @@ export class LikeC4ModelParser {
539
548
  }
540
549
  return acc
541
550
  }
542
- if (ast.isNotationProperty(prop)) {
543
- if (isTruthy(prop.value)) {
544
- acc.customRelation[prop.key] = removeIndent(prop.value)
551
+ if (ast.isRelationNavigateToProperty(prop)) {
552
+ const viewId = prop.value.view.ref?.name
553
+ if (isTruthy(viewId)) {
554
+ acc.customRelation.navigateTo = viewId as c4.ViewID
545
555
  }
546
556
  return acc
547
557
  }
@@ -624,6 +634,12 @@ export class LikeC4ModelParser {
624
634
  }
625
635
  }
626
636
 
637
+ private parseDynamicParallelSteps(node: ast.DynamicViewParallelSteps): c4.DynamicViewParallelSteps {
638
+ return {
639
+ __parallel: node.steps.map(step => this.parseDynamicStep(step))
640
+ }
641
+ }
642
+
627
643
  private parseDynamicStep(node: ast.DynamicViewStep): c4.DynamicViewStep {
628
644
  const sourceEl = elementRef(node.source)
629
645
  if (!sourceEl) {
@@ -635,9 +651,7 @@ export class LikeC4ModelParser {
635
651
  }
636
652
  let source = this.resolveFqn(sourceEl)
637
653
  let target = this.resolveFqn(targetEl)
638
- const title = removeIndent(
639
- node.title ?? node.custom?.props.find((p): p is ast.RelationStringProperty => p.key === 'title')?.value
640
- ) ?? ''
654
+ const title = removeIndent(node.title) ?? null
641
655
 
642
656
  let step: Writable<c4.DynamicViewStep> = {
643
657
  source,
@@ -655,31 +669,35 @@ export class LikeC4ModelParser {
655
669
  if (Array.isArray(node.custom?.props)) {
656
670
  for (const prop of node.custom.props) {
657
671
  try {
658
- if (ast.isRelationStringProperty(prop)) {
659
- const value = removeIndent(prop.value)
660
- if (isTruthy(value) && prop.key !== 'title') {
661
- step[prop.key] = value
672
+ if (ast.isRelationStringProperty(prop) || ast.isNotationProperty(prop) || ast.isNotesProperty(prop)) {
673
+ if (isDefined(prop.value)) {
674
+ step[prop.key] = removeIndent(prop.value) ?? ''
662
675
  }
663
676
  continue
664
677
  }
665
678
  if (ast.isArrowProperty(prop)) {
666
- step[prop.key] = prop.value
679
+ if (isDefined(prop.value)) {
680
+ step[prop.key] = prop.value
681
+ }
667
682
  continue
668
683
  }
669
684
  if (ast.isColorProperty(prop)) {
670
685
  const value = toColor(prop)
671
- if (isTruthy(value)) {
686
+ if (isDefined(value)) {
672
687
  step[prop.key] = value
673
688
  }
674
689
  continue
675
690
  }
676
691
  if (ast.isLineProperty(prop)) {
677
- step[prop.key] = prop.value
692
+ if (isDefined(prop.value)) {
693
+ step[prop.key] = prop.value
694
+ }
678
695
  continue
679
696
  }
680
- if (ast.isNotationProperty(prop)) {
681
- if (isTruthy(prop.value)) {
682
- step[prop.key] = prop.value
697
+ if (ast.isRelationNavigateToProperty(prop)) {
698
+ const viewId = prop.value.view.ref?.name
699
+ if (isTruthy(viewId)) {
700
+ step.navigateTo = viewId as c4.ViewID
683
701
  }
684
702
  continue
685
703
  }
@@ -845,13 +863,17 @@ export class LikeC4ModelParser {
845
863
  steps: body.steps.reduce((acc, n) => {
846
864
  try {
847
865
  if (isValid(n)) {
848
- acc.push(this.parseDynamicStep(n))
866
+ if (ast.isDynamicViewParallelSteps(n)) {
867
+ acc.push(this.parseDynamicParallelSteps(n))
868
+ } else {
869
+ acc.push(this.parseDynamicStep(n))
870
+ }
849
871
  }
850
872
  } catch (e) {
851
873
  logWarnError(e)
852
874
  }
853
875
  return acc
854
- }, [] as c4.DynamicViewStep[]),
876
+ }, [] as c4.DynamicViewStepOrParallel[]),
855
877
  ...(manualLayout && { manualLayout })
856
878
  }
857
879
  }