@diagrammo/dgmo 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.cjs +133 -280
- package/dist/advanced.d.cts +9 -2
- package/dist/advanced.d.ts +9 -2
- package/dist/advanced.js +133 -280
- package/dist/auto.cjs +135 -269
- package/dist/auto.js +96 -96
- package/dist/auto.mjs +135 -269
- package/dist/cli.cjs +125 -125
- package/dist/index.cjs +132 -266
- package/dist/index.js +132 -266
- package/dist/internal.cjs +133 -280
- package/dist/internal.d.cts +9 -2
- package/dist/internal.d.ts +9 -2
- package/dist/internal.js +133 -280
- package/docs/language-reference.md +14 -18
- package/docs/migration-sequence-color-to-tags.md +1 -1
- package/gallery/fixtures/sequence-tags-protocols.dgmo +3 -3
- package/gallery/fixtures/sequence-tags.dgmo +3 -3
- package/gallery/fixtures/sequence.dgmo +4 -4
- package/package.json +7 -3
- package/src/auto/index.ts +2 -2
- package/src/boxes-and-lines/layout.ts +1 -2
- package/src/c4/parser.ts +1 -1
- package/src/class/parser.ts +1 -1
- package/src/cli.ts +2 -2
- package/src/completion.ts +1 -14
- package/src/cycle/parser.ts +1 -1
- package/src/d3.ts +2 -3
- package/src/diagnostics.ts +20 -0
- package/src/echarts.ts +2 -2
- package/src/editor/dgmo.grammar.d.ts +1 -1
- package/src/er/parser.ts +1 -1
- package/src/graph/flowchart-renderer.ts +3 -0
- package/src/infra/renderer.ts +1 -2
- package/src/journey-map/parser.ts +1 -1
- package/src/kanban/parser.ts +1 -1
- package/src/mindmap/parser.ts +2 -3
- package/src/org/parser.ts +1 -1
- package/src/pert/analyzer.ts +10 -10
- package/src/pert/layout.ts +1 -1
- package/src/pert/parser.ts +1 -1
- package/src/pyramid/parser.ts +1 -1
- package/src/raci/parser.ts +2 -2
- package/src/ring/parser.ts +1 -1
- package/src/sequence/parser.ts +66 -14
- package/src/sequence/participant-inference.ts +18 -181
- package/src/sequence/renderer.ts +47 -136
- package/src/sitemap/parser.ts +1 -1
- package/src/tech-radar/parser.ts +2 -2
- package/src/utils/extract-alias.ts +1 -1
- package/src/utils/inline-markdown.ts +1 -1
- package/src/utils/time-ticks.ts +1 -1
- package/src/wireframe/parser.ts +1 -1
|
@@ -263,7 +263,7 @@ quote.
|
|
|
263
263
|
|
|
264
264
|
### 2.3 Examples
|
|
265
265
|
|
|
266
|
-
- `Auth
|
|
266
|
+
- `Auth Database is a database` — bare multi-word, no quoting needed
|
|
267
267
|
- `"first name" varchar` — quote when name contains a reserved char (the `:` ER type separator)
|
|
268
268
|
- `"Order | Items"` — quote the pipe
|
|
269
269
|
- `class "Customer Service"` — bare multi-word also accepts in class
|
|
@@ -289,6 +289,7 @@ These are intentionally outside the universal rule:
|
|
|
289
289
|
- `I_NAME_MERGED` (warning) — two source-distinct names normalize to the same key with different displayed forms
|
|
290
290
|
- `E_NAME_RESERVED_CHAR` (error) — bare name contains a reserved char without quoting
|
|
291
291
|
- `E_AKA_REMOVED` (error) — removed `aka` keyword used in sequence participant declaration
|
|
292
|
+
- `E_PARTICIPANT_TYPE_REMOVED` (error) — sequence `is a X` declaration used a removed type keyword (`service`, `frontend`, `networking`, `gateway`, `external`)
|
|
292
293
|
|
|
293
294
|
---
|
|
294
295
|
|
|
@@ -302,7 +303,7 @@ tag-shorthand and venn `alias` keyword forms with a uniform rule.
|
|
|
302
303
|
|
|
303
304
|
```
|
|
304
305
|
sequence
|
|
305
|
-
Alice is
|
|
306
|
+
Alice is an actor as a
|
|
306
307
|
Bob is a database as b
|
|
307
308
|
a -hello-> b
|
|
308
309
|
b -ack-> a
|
|
@@ -376,43 +377,38 @@ Name is a <type> [position N]
|
|
|
376
377
|
Name | key: value
|
|
377
378
|
```
|
|
378
379
|
|
|
379
|
-
Types: `
|
|
380
|
+
Types: `actor`, `database`, `cache`, `queue` (plus default — the plain rectangle, used when `is a` is omitted).
|
|
381
|
+
|
|
382
|
+
Type names in `is a X` are **case-insensitive** (`is a Actor`, `is an ACTOR`, `is an actor` all parse the same). The keywords `service`, `frontend`, `networking`, `gateway`, and `external` were removed in 0.16.0 and now emit `E_PARTICIPANT_TYPE_REMOVED`; drop the override and the participant renders as the default rectangle.
|
|
383
|
+
|
|
384
|
+
A participant *named* with a removed-type keyword (e.g. `service -> User: hi` declares a participant named "service") remains valid. The trim affects only the `is a X` declaration syntax, not name resolution.
|
|
380
385
|
|
|
381
386
|
**Inference rules** — the parser infers the type (and shape) from the participant name. Only use `is a` when the name does not match or you want to override:
|
|
382
387
|
|
|
383
388
|
| Inferred Type | Shape | Name Patterns (examples) |
|
|
384
389
|
|--------------|-------|--------------------------|
|
|
385
|
-
| actor | Stick figure | `User`, `Customer`, `
|
|
386
|
-
|
|
|
387
|
-
| database | Cylinder (vertical) | `*DB`, `Database`, `*Store`, `Storage`, `*Repo`, `SQL`, Postgres, MySQL, Mongo, Dynamo, Aurora, Spanner, Supabase, Firebase, BigQuery, Redshift, Snowflake, Cassandra, Neo4j, ClickHouse, Elastic, OpenSearch, Pinecone, Weaviate, `*Table` |
|
|
390
|
+
| actor | Stick figure | `User`, `Customer`, `Admin`, `Agent`, `Person`, `Buyer`, `Seller`, `Guest`, `Visitor`, `Operator`, `Developer`, Alice, Bob, Charlie, Fan, Purchaser, Reviewer, `*User`, `*Actor`, `*Analyst`, `*Staff` |
|
|
391
|
+
| database | Cylinder (vertical) | `*DB`, `Database`, `Datastore`, `*Store`, `Storage`, `*Repo`, `Repository`, `SQL`, Postgres, MySQL, Mongo, Dynamo, Aurora, Spanner, Supabase, Firebase, BigQuery, Redshift, Snowflake, Cassandra, Neo4j, ClickHouse, Elastic, OpenSearch, Druid, Trino, Pinecone, Weaviate, Qdrant, Milvus, Presto, `*Table` |
|
|
388
392
|
| cache | Dashed cylinder | `*Cache`, Redis, Memcache, KeyDB, Dragonfly, Hazelcast, Valkey |
|
|
389
|
-
| queue | Horizontal cylinder (pipe) | `*Queue`, `*MQ`, SQS, Kafka, RabbitMQ, `EventBus`, `*Bus`, `Topic`, `*Stream`, SNS, PubSub, NATS, Pulsar, Kinesis, EventBridge, Celery, Sidekiq, `*Channel
|
|
390
|
-
| networking | Hexagon | `*Router`, `*Balancer`, `Gateway`, `Proxy`, `LB`, `CDN`, `Firewall`, `WAF`, `DNS`, `Ingress`, Nginx, Traefik, Envoy, Istio, Kong, Akamai, Cloudflare, `*Mesh` |
|
|
391
|
-
| frontend | Monitor (screen + stand) | `*App`, `Application`, `Mobile`, iOS, Android, `Web`, `Browser`, `Frontend`, `*UI`, `Dashboard`, `*CLI`, `Terminal`, React, Vue, Angular, Svelte, NextJS, Electron, Tauri, `*Widget`, `Portal`, `*Console`, SPA, PWA |
|
|
392
|
-
| gateway | Rectangle (same as default) | matched via `is a gateway` only |
|
|
393
|
-
| external | Dashed rectangle | `External`, `*Ext`, `ThirdParty`, `*3P`, `Vendor`, `Webhook`, `Upstream`, `Downstream`, `Callback`, AWS, GCP, Azure |
|
|
393
|
+
| queue | Horizontal cylinder (pipe) | `*Queue`, `*MQ`, SQS, Kafka, RabbitMQ, `EventBus`, `MessageBus`, `*Bus`, `Topic`, `*Stream`, SNS, PubSub, `*Broker`, NATS, Pulsar, Kinesis, EventBridge, CloudEvents, Celery, Sidekiq, EventHub, `*Channel` |
|
|
394
394
|
| default | Rectangle | Everything else (no `is a` needed) |
|
|
395
395
|
|
|
396
396
|
**Inference handles it (skip `is a`):**
|
|
397
397
|
```
|
|
398
|
-
AuthService // service (matches *Service)
|
|
399
398
|
PostgresDB // database (matches *DB)
|
|
400
399
|
Redis // cache (exact match)
|
|
401
400
|
User // actor (exact match)
|
|
402
401
|
Kafka // queue (exact match)
|
|
403
|
-
API Gateway // networking (matches Gateway)
|
|
404
|
-
WebApp // frontend (matches *App)
|
|
405
|
-
Stripe // service (exact match)
|
|
406
402
|
```
|
|
407
403
|
|
|
408
404
|
**Inference would miss (use `is a`):**
|
|
409
405
|
```
|
|
410
|
-
|
|
411
|
-
Vault is a database // "Vault" infers as service, but you want database
|
|
406
|
+
Vault is a database // "Vault" matches no rule, but you want database
|
|
412
407
|
Notifications is a queue // "Notifications" matches no rule
|
|
413
|
-
Analytics is a frontend // "Analytics" matches no rule
|
|
414
408
|
```
|
|
415
409
|
|
|
410
|
+
Names that previously inferred to a removed type — `AuthService`, `WebApp`, `Cloudflare`, `API Gateway`, `Stripe`, `Webhook` — now fall through to default (plain rectangle). That is the intended outcome of the trim: the visual differentiation is gone because the underlying distinction did not pull its weight.
|
|
411
|
+
|
|
416
412
|
### 2.2 Participant Groups
|
|
417
413
|
|
|
418
414
|
```
|
|
@@ -14,9 +14,9 @@ tag Owner as o
|
|
|
14
14
|
Data blue
|
|
15
15
|
|
|
16
16
|
Buyer is an actor
|
|
17
|
-
CheckoutSvc
|
|
18
|
-
InventorySvc
|
|
19
|
-
PaymentSvc
|
|
17
|
+
CheckoutSvc | o: Checkout
|
|
18
|
+
InventorySvc | o: Fulfillment
|
|
19
|
+
PaymentSvc | o: Payments
|
|
20
20
|
OrderDB is a database | o: Data
|
|
21
21
|
EventBus is a queue
|
|
22
22
|
|
|
@@ -13,12 +13,12 @@ tag Team as t
|
|
|
13
13
|
Security red
|
|
14
14
|
|
|
15
15
|
Mobile is an actor
|
|
16
|
-
Gateway
|
|
16
|
+
Gateway | t: Platform
|
|
17
17
|
Redis is a cache | c: Caching, t: Platform
|
|
18
18
|
|
|
19
19
|
[Backend] | t: Product
|
|
20
|
-
UserAPI
|
|
21
|
-
OrderAPI
|
|
20
|
+
UserAPI
|
|
21
|
+
OrderAPI
|
|
22
22
|
DB is a database
|
|
23
23
|
|
|
24
24
|
== Authentication ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
"prebuild": "rm -rf dist && pnpm codegen",
|
|
118
118
|
"build": "tsup",
|
|
119
119
|
"typecheck": "tsc --noEmit",
|
|
120
|
+
"typecheck:strict": "tsc --noEmit -p tsconfig.strict.json",
|
|
120
121
|
"dev": "tsup --watch",
|
|
121
122
|
"pretest": "pnpm codegen",
|
|
122
123
|
"test": "vitest run --coverage",
|
|
@@ -130,12 +131,14 @@
|
|
|
130
131
|
"lint:fix": "eslint . --fix",
|
|
131
132
|
"format": "prettier --write src/",
|
|
132
133
|
"format:check": "prettier --check src/",
|
|
134
|
+
"check:api": "pnpm build && bash scripts/check-api.sh check",
|
|
135
|
+
"check:api:update": "pnpm build && bash scripts/check-api.sh update",
|
|
133
136
|
"check:duplication": "jscpd ./src",
|
|
134
137
|
"check:deadcode": "knip",
|
|
135
138
|
"check:spelling": "cspell \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
136
|
-
"check:all": "pnpm check:deadcode && pnpm check:spelling && pnpm check:duplication && pnpm check:circular && pnpm check:deps && pnpm check:security && pnpm build && pnpm check:publish && pnpm check:types",
|
|
139
|
+
"check:all": "pnpm check:deadcode && pnpm check:spelling && pnpm check:duplication && pnpm check:circular && pnpm check:deps && pnpm check:security && pnpm build && bash scripts/check-api.sh check && pnpm check:publish && pnpm check:types",
|
|
137
140
|
"check:circular": "madge --circular --extensions ts src/ --json | node -e \"const c=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); const n=c.length; if(n>4){console.error('New circular deps found ('+n+' > 4 known type-only cycles)');process.exit(1)}else if(n>0){console.log(n+' known type-only/dynamic cycles (safe)')}else{console.log('No circular dependencies')}\"",
|
|
138
|
-
"check:deps": "depcheck --ignores='@codemirror/language,@lezer/*,husky,lint-staged,tsup,axe-core'",
|
|
141
|
+
"check:deps": "depcheck --ignores='@codemirror/language,@lezer/*,husky,lint-staged,tsup,axe-core,type-coverage'",
|
|
139
142
|
"check:security": "pnpm audit --prod",
|
|
140
143
|
"check:publish": "publint",
|
|
141
144
|
"check:size": "pnpm build && du -sh dist/ && echo '---' && ls -lh dist/*.js dist/*.cjs",
|
|
@@ -185,6 +188,7 @@
|
|
|
185
188
|
"prettier": "^3.8.2",
|
|
186
189
|
"publint": "^0.3.18",
|
|
187
190
|
"tsup": "^8.5.1",
|
|
191
|
+
"type-coverage": "^2.29.7",
|
|
188
192
|
"typescript": "^6.0.2",
|
|
189
193
|
"typescript-eslint": "^8.58.1",
|
|
190
194
|
"vitest": "^4.1.4"
|
package/src/auto/index.ts
CHANGED
|
@@ -279,7 +279,7 @@ export function resolveTheme(
|
|
|
279
279
|
function ensureStyles(): void {
|
|
280
280
|
if (typeof document === 'undefined') return;
|
|
281
281
|
const html = document.documentElement;
|
|
282
|
-
if (html
|
|
282
|
+
if (html?.dataset?.[STYLE_FLAG] === '1') return;
|
|
283
283
|
|
|
284
284
|
// If a <link rel="stylesheet"> for our css is already linked, skip inline.
|
|
285
285
|
const linked = document.querySelector(
|
|
@@ -509,7 +509,7 @@ function determineReplaceTarget(matched: Element): Element {
|
|
|
509
509
|
// If matched is a <code> whose only child of <pre> is itself, replace <pre>.
|
|
510
510
|
if (matched.tagName === 'CODE') {
|
|
511
511
|
const parent = matched.parentElement;
|
|
512
|
-
if (parent
|
|
512
|
+
if (parent?.tagName === 'PRE') {
|
|
513
513
|
const meaningfulChildren = Array.from(parent.childNodes).filter(
|
|
514
514
|
(n) =>
|
|
515
515
|
n.nodeType === 1 ||
|
|
@@ -636,8 +636,7 @@ export async function layoutBoxesAndLines(
|
|
|
636
636
|
const edge = parsed.edges[i];
|
|
637
637
|
if (edgeParallelCounts[i] === 0) continue;
|
|
638
638
|
const elkEdge = edgeById.get(`e${i}`);
|
|
639
|
-
if (!elkEdge
|
|
640
|
-
continue;
|
|
639
|
+
if (!elkEdge?.sections || elkEdge.sections.length === 0) continue;
|
|
641
640
|
const container = elkEdge.container ?? 'root';
|
|
642
641
|
const off = containerAbs.get(container) ?? { x: 0, y: 0 };
|
|
643
642
|
const s = elkEdge.sections[0];
|
package/src/c4/parser.ts
CHANGED
package/src/class/parser.ts
CHANGED
|
@@ -259,7 +259,7 @@ export function parseClassDiagram(
|
|
|
259
259
|
// First line: bare chart type + optional title (new syntax)
|
|
260
260
|
if (!contentStarted && indent === 0 && i === 0) {
|
|
261
261
|
const firstLine = parseFirstLine(trimmed);
|
|
262
|
-
if (firstLine
|
|
262
|
+
if (firstLine?.chartType === 'class') {
|
|
263
263
|
if (firstLine.title) {
|
|
264
264
|
result.title = firstLine.title;
|
|
265
265
|
result.titleLineNumber = lineNumber;
|
package/src/cli.ts
CHANGED
|
@@ -190,8 +190,8 @@ title: Auth Flow
|
|
|
190
190
|
|
|
191
191
|
// Participants auto-inferred, or declare explicitly:
|
|
192
192
|
User is an actor
|
|
193
|
-
API is a service
|
|
194
193
|
DB is a database
|
|
194
|
+
Cache is a cache
|
|
195
195
|
|
|
196
196
|
User -Login-> API
|
|
197
197
|
API -Find user-> DB
|
|
@@ -1157,7 +1157,7 @@ async function main(): Promise<void> {
|
|
|
1157
1157
|
}
|
|
1158
1158
|
|
|
1159
1159
|
const existingDgmo = config.mcpServers?.['dgmo'];
|
|
1160
|
-
if (existingDgmo
|
|
1160
|
+
if (existingDgmo?.command === 'dgmo-mcp') {
|
|
1161
1161
|
console.log(`✓ dgmo MCP server already configured in ${configPath}`);
|
|
1162
1162
|
} else {
|
|
1163
1163
|
if (existingDgmo) {
|
package/src/completion.ts
CHANGED
|
@@ -595,20 +595,7 @@ export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
|
|
|
595
595
|
* C4_IS_A_RE).
|
|
596
596
|
*/
|
|
597
597
|
export const ENTITY_TYPES = new Map<string, string[]>([
|
|
598
|
-
[
|
|
599
|
-
'sequence',
|
|
600
|
-
[
|
|
601
|
-
'service',
|
|
602
|
-
'database',
|
|
603
|
-
'actor',
|
|
604
|
-
'queue',
|
|
605
|
-
'cache',
|
|
606
|
-
'gateway',
|
|
607
|
-
'external',
|
|
608
|
-
'networking',
|
|
609
|
-
'frontend',
|
|
610
|
-
],
|
|
611
|
-
],
|
|
598
|
+
['sequence', ['actor', 'database', 'queue', 'cache']],
|
|
612
599
|
[
|
|
613
600
|
'c4',
|
|
614
601
|
['person', 'system', 'container', 'component', 'external', 'database'],
|
package/src/cycle/parser.ts
CHANGED
|
@@ -103,7 +103,7 @@ export function parseCycle(content: string): ParsedCycle {
|
|
|
103
103
|
// ── First line: chart type declaration ──
|
|
104
104
|
if (!headerParsed) {
|
|
105
105
|
const firstLineResult = parseFirstLine(trimmed);
|
|
106
|
-
if (firstLineResult
|
|
106
|
+
if (firstLineResult?.chartType === 'cycle') {
|
|
107
107
|
result.title = firstLineResult.title ?? '';
|
|
108
108
|
result.titleLineNumber = lineNum;
|
|
109
109
|
headerParsed = true;
|
package/src/d3.ts
CHANGED
|
@@ -496,7 +496,7 @@ export function parseVisualization(
|
|
|
496
496
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
497
497
|
};
|
|
498
498
|
|
|
499
|
-
if (!content
|
|
499
|
+
if (!content?.trim()) {
|
|
500
500
|
return fail(0, 'Empty content');
|
|
501
501
|
}
|
|
502
502
|
|
|
@@ -4107,8 +4107,7 @@ function renderTimelineTagLegendOverlay(
|
|
|
4107
4107
|
groupEl.attr('data-tag-group', groupKey);
|
|
4108
4108
|
if (isActive && !viewMode) {
|
|
4109
4109
|
const isSwimActive =
|
|
4110
|
-
currentSwimlaneGroup
|
|
4111
|
-
currentSwimlaneGroup.toLowerCase() === groupKey;
|
|
4110
|
+
currentSwimlaneGroup?.toLowerCase() === groupKey;
|
|
4112
4111
|
const pillWidth =
|
|
4113
4112
|
measureLegendText(groupName, LG_PILL_FONT_SIZE) + LG_PILL_PAD;
|
|
4114
4113
|
const pillXOff = LG_CAPSULE_PAD;
|
package/src/diagnostics.ts
CHANGED
|
@@ -121,6 +121,14 @@ export const NAME_DIAGNOSTIC_CODES = {
|
|
|
121
121
|
* unnecessary; the diagnostic directs users to the new syntax.
|
|
122
122
|
*/
|
|
123
123
|
AKA_REMOVED: 'E_AKA_REMOVED',
|
|
124
|
+
/**
|
|
125
|
+
* Error: a removed sequence participant-type keyword was used in
|
|
126
|
+
* an `is a X` declaration. The 0.16.0 trim retained only
|
|
127
|
+
* `actor`/`database`/`cache`/`queue`; `service`/`frontend`/
|
|
128
|
+
* `networking`/`gateway`/`external` no longer carry semantic
|
|
129
|
+
* weight and emit this error so users drop the override.
|
|
130
|
+
*/
|
|
131
|
+
PARTICIPANT_TYPE_REMOVED: 'E_PARTICIPANT_TYPE_REMOVED',
|
|
124
132
|
} as const;
|
|
125
133
|
|
|
126
134
|
/**
|
|
@@ -152,6 +160,18 @@ export function akaRemovedMessage(): string {
|
|
|
152
160
|
return `'aka' is no longer supported — use the participant name directly`;
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Canonical message for `E_PARTICIPANT_TYPE_REMOVED`. Emitted when a
|
|
165
|
+
* sequence participant declaration uses a removed type keyword
|
|
166
|
+
* (`service`, `frontend`, `networking`, `gateway`, `external`).
|
|
167
|
+
*/
|
|
168
|
+
export function participantTypeRemovedMessage(type: string): string {
|
|
169
|
+
return (
|
|
170
|
+
`'${type}' is no longer supported — drop 'is a ${type}'; ` +
|
|
171
|
+
`the participant renders as the default rectangle`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
155
175
|
// ============================================================
|
|
156
176
|
// Universal Alias Syntax diagnostic codes (TD-18)
|
|
157
177
|
// ============================================================
|
package/src/echarts.ts
CHANGED
|
@@ -467,7 +467,7 @@ export function parseExtendedChart(
|
|
|
467
467
|
)
|
|
468
468
|
: trimmed;
|
|
469
469
|
const dataRow = parseDataRowValues(strippedLine);
|
|
470
|
-
if (dataRow
|
|
470
|
+
if (dataRow?.values.length === 1) {
|
|
471
471
|
const source = sankeyStack.at(-1)!.name;
|
|
472
472
|
const linkColor = valColorMatch?.[2]
|
|
473
473
|
? resolveColorWithDiagnostic(
|
|
@@ -745,7 +745,7 @@ export function parseExtendedChart(
|
|
|
745
745
|
|
|
746
746
|
// Funnel / generic data point: "Label value"
|
|
747
747
|
const dataRow = parseDataRowValues(trimmed);
|
|
748
|
-
if (dataRow
|
|
748
|
+
if (dataRow?.values.length === 1) {
|
|
749
749
|
const { label: rawLabel, color: pointColor } = extractColor(
|
|
750
750
|
dataRow.label,
|
|
751
751
|
palette
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { LRParser } from '@lezer/lr';
|
|
1
|
+
import type { LRParser } from '@lezer/lr';
|
|
2
2
|
export declare const parser: LRParser;
|
package/src/er/parser.ts
CHANGED
|
@@ -308,7 +308,7 @@ export function parseERDiagram(
|
|
|
308
308
|
// First line: chart type + optional title
|
|
309
309
|
if (!firstLineParsed && indent === 0) {
|
|
310
310
|
const firstLineResult = parseFirstLine(trimmed);
|
|
311
|
-
if (firstLineResult
|
|
311
|
+
if (firstLineResult?.chartType === 'er') {
|
|
312
312
|
firstLineParsed = true;
|
|
313
313
|
if (firstLineResult.title) {
|
|
314
314
|
result.title = firstLineResult.title;
|
package/src/infra/renderer.ts
CHANGED
|
@@ -1733,8 +1733,7 @@ function renderNodes(
|
|
|
1733
1733
|
}
|
|
1734
1734
|
|
|
1735
1735
|
// Role badge dots — only shown when Capabilities legend is expanded
|
|
1736
|
-
const showDots =
|
|
1737
|
-
activeGroup != null && activeGroup.toLowerCase() === 'capabilities';
|
|
1736
|
+
const showDots = activeGroup?.toLowerCase() === 'capabilities';
|
|
1738
1737
|
const roles = showDots && !node.isEdge ? inferRoles(node.properties) : [];
|
|
1739
1738
|
if (roles.length > 0) {
|
|
1740
1739
|
// Move dots up above the collapse bar for collapsed groups
|
package/src/kanban/parser.ts
CHANGED
package/src/mindmap/parser.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function parseMindmap(
|
|
|
62
62
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
if (!content
|
|
65
|
+
if (!content?.trim()) {
|
|
66
66
|
return fail(0, 'No content provided');
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -278,8 +278,7 @@ export function parseMindmap(
|
|
|
278
278
|
result.diagnostics.push(diag);
|
|
279
279
|
result.error = formatDgmoError(diag);
|
|
280
280
|
} else if (
|
|
281
|
-
titleRoot &&
|
|
282
|
-
titleRoot.children.length === 0 &&
|
|
281
|
+
titleRoot?.children.length === 0 &&
|
|
283
282
|
result.roots.length === 1 &&
|
|
284
283
|
!result.error
|
|
285
284
|
) {
|
package/src/org/parser.ts
CHANGED
|
@@ -115,7 +115,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
|
115
115
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
-
if (!content
|
|
118
|
+
if (!content?.trim()) {
|
|
119
119
|
return fail(0, 'No content provided');
|
|
120
120
|
}
|
|
121
121
|
|
package/src/pert/analyzer.ts
CHANGED
|
@@ -180,7 +180,7 @@ export function analyzePert(parsed: ParsedPert): ResolvedPert {
|
|
|
180
180
|
for (const e of edges) {
|
|
181
181
|
if (!e.lag || e.lag.amount >= 0) continue;
|
|
182
182
|
const src = activities.find((a) => a.id === e.source);
|
|
183
|
-
if (!src
|
|
183
|
+
if (!src?.duration) continue;
|
|
184
184
|
const leadDays = -toDays(e.lag, sprintDays);
|
|
185
185
|
const srcDurDays = toDays(src.duration.m, sprintDays);
|
|
186
186
|
if (e.type === 'FS' && leadDays > srcDurDays) {
|
|
@@ -923,7 +923,7 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
923
923
|
// Expected duration AND each percentile latest-safe start so the
|
|
924
924
|
// caption shape stays parallel to the feasible case (one top row +
|
|
925
925
|
// three percentile sub-rows).
|
|
926
|
-
if (anchor
|
|
926
|
+
if (anchor?.kind === 'backward') {
|
|
927
927
|
return [
|
|
928
928
|
{ text: 'Expected duration: ?', level: 0 },
|
|
929
929
|
{ text: 'P50 latest-safe start: ?', level: 0 },
|
|
@@ -958,13 +958,13 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
958
958
|
const sigmaParen = showMcDetail
|
|
959
959
|
? ` (± ${roundForCaption(projectSigma!)} ${pluralizeUnit(projectSigma!, unit)})`
|
|
960
960
|
: '';
|
|
961
|
-
if (anchor
|
|
961
|
+
if (anchor?.kind === 'forward') {
|
|
962
962
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
963
963
|
rows.push({
|
|
964
964
|
text: `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)}${sigmaParen}.`,
|
|
965
965
|
level: 0,
|
|
966
966
|
});
|
|
967
|
-
} else if (anchor
|
|
967
|
+
} else if (anchor?.kind === 'backward') {
|
|
968
968
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
969
969
|
rows.push({
|
|
970
970
|
text: `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)}${sigmaParen}.`,
|
|
@@ -990,13 +990,13 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
990
990
|
{ pct: 80, days: monteCarloResult!.p80 },
|
|
991
991
|
{ pct: 95, days: monteCarloResult!.p95 },
|
|
992
992
|
];
|
|
993
|
-
if (anchor
|
|
993
|
+
if (anchor?.kind === 'forward') {
|
|
994
994
|
for (const { pct, days } of percentiles) {
|
|
995
995
|
const offsetDays = roundConservative(days, 'forward');
|
|
996
996
|
const date = addCalendarDays(anchor.date, offsetDays);
|
|
997
997
|
rows.push({ text: `P${pct} finish: ${date}.`, level: 1 });
|
|
998
998
|
}
|
|
999
|
-
} else if (anchor
|
|
999
|
+
} else if (anchor?.kind === 'backward') {
|
|
1000
1000
|
for (const { pct, days } of percentiles) {
|
|
1001
1001
|
const offsetDays = roundConservative(days, 'backward');
|
|
1002
1002
|
const date = addCalendarDays(anchor.date, -offsetDays);
|
|
@@ -1074,10 +1074,10 @@ export function buildProjectSubtitle(input: {
|
|
|
1074
1074
|
|
|
1075
1075
|
if (projectMu === null) {
|
|
1076
1076
|
// Anchored + TBD: keep the framing prefix, mark the math as ?.
|
|
1077
|
-
if (anchor
|
|
1077
|
+
if (anchor?.kind === 'forward') {
|
|
1078
1078
|
return `Expected finish: ? · ≈ ? ${pluralizeUnit(2, unit)} of work`;
|
|
1079
1079
|
}
|
|
1080
|
-
if (anchor
|
|
1080
|
+
if (anchor?.kind === 'backward') {
|
|
1081
1081
|
return `Expected start: ? · ≈ ? ${pluralizeUnit(2, unit)} lead time`;
|
|
1082
1082
|
}
|
|
1083
1083
|
// Unanchored + TBD: surface that the total is unknown. The per-node
|
|
@@ -1087,11 +1087,11 @@ export function buildProjectSubtitle(input: {
|
|
|
1087
1087
|
|
|
1088
1088
|
const muStr = `${roundForCaption(projectMu)} ${pluralizeUnit(projectMu, unit)}`;
|
|
1089
1089
|
|
|
1090
|
-
if (anchor
|
|
1090
|
+
if (anchor?.kind === 'forward') {
|
|
1091
1091
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
1092
1092
|
return `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)} · ≈ ${muStr} of work${sigmaParen}`;
|
|
1093
1093
|
}
|
|
1094
|
-
if (anchor
|
|
1094
|
+
if (anchor?.kind === 'backward') {
|
|
1095
1095
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
1096
1096
|
return `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)} · ≈ ${muStr} lead time${sigmaParen}`;
|
|
1097
1097
|
}
|
package/src/pert/layout.ts
CHANGED
|
@@ -208,7 +208,7 @@ function nodeDimensions(
|
|
|
208
208
|
sizing: NodeSizing,
|
|
209
209
|
overrides?: LayoutOverrides
|
|
210
210
|
): { width: number; height: number } {
|
|
211
|
-
if (overrides
|
|
211
|
+
if (overrides?.[id]) {
|
|
212
212
|
return { width: overrides[id].width, height: overrides[id].height };
|
|
213
213
|
}
|
|
214
214
|
const r = resolved.activities.find((a) => a.activity.id === id);
|
package/src/pert/parser.ts
CHANGED
|
@@ -823,7 +823,7 @@ export function parsePert(
|
|
|
823
823
|
const head = trimmed.slice(0, firstSpace).toLowerCase();
|
|
824
824
|
const value = trimmed.slice(firstSpace + 1).trim();
|
|
825
825
|
const hint = NEAR_DIRECTIVE_HINTS.find((h) => h.stem === head);
|
|
826
|
-
if (hint
|
|
826
|
+
if (hint?.matches.test(value)) {
|
|
827
827
|
error(
|
|
828
828
|
lineNumber,
|
|
829
829
|
`Unknown directive '${head}'. Did you mean '${hint.canonical}'?`,
|
package/src/pyramid/parser.ts
CHANGED
|
@@ -89,7 +89,7 @@ export function parsePyramid(content: string): ParsedPyramid {
|
|
|
89
89
|
// ── First line: chart type declaration ──
|
|
90
90
|
if (!headerParsed) {
|
|
91
91
|
const firstLineResult = parseFirstLine(trimmed);
|
|
92
|
-
if (firstLineResult
|
|
92
|
+
if (firstLineResult?.chartType === 'pyramid') {
|
|
93
93
|
result.title = firstLineResult.title ?? '';
|
|
94
94
|
result.titleLineNumber = lineNum;
|
|
95
95
|
headerParsed = true;
|
package/src/raci/parser.ts
CHANGED
|
@@ -178,7 +178,7 @@ export function parseRaci(
|
|
|
178
178
|
result.diagnostics.push(makeDgmoError(line, message, 'error', code));
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
-
if (!content
|
|
181
|
+
if (!content?.trim()) {
|
|
182
182
|
return fail(0, 'No content provided');
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -624,7 +624,7 @@ export function parseRaci(
|
|
|
624
624
|
// detect by whether it was declared before `bodyStarted`.
|
|
625
625
|
// We piggyback the `declaredLine` we recorded.
|
|
626
626
|
const entry = roleStore.get(roleId);
|
|
627
|
-
if (entry
|
|
627
|
+
if (entry?.declaredLine === lineNumber) {
|
|
628
628
|
const candidates = result.roleDisplayNames.filter(
|
|
629
629
|
(n) => n !== entry.displayName
|
|
630
630
|
);
|
package/src/ring/parser.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function parseRing(content: string): ParsedRing {
|
|
|
74
74
|
// ── First line: chart type declaration ──
|
|
75
75
|
if (!headerParsed) {
|
|
76
76
|
const firstLineResult = parseFirstLine(trimmed);
|
|
77
|
-
if (firstLineResult
|
|
77
|
+
if (firstLineResult?.chartType === 'ring') {
|
|
78
78
|
result.title = firstLineResult.title ?? '';
|
|
79
79
|
result.titleLineNumber = lineNum;
|
|
80
80
|
headerParsed = true;
|