@diagrammo/dgmo 0.7.3 → 0.8.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 (62) hide show
  1. package/AGENTS.md +15 -20
  2. package/README.md +56 -58
  3. package/dist/cli.cjs +188 -181
  4. package/dist/index.cjs +3522 -1072
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +196 -43
  7. package/dist/index.d.ts +196 -43
  8. package/dist/index.js +3509 -1072
  9. package/dist/index.js.map +1 -1
  10. package/docs/language-reference.md +629 -289
  11. package/package.json +1 -1
  12. package/src/c4/layout.ts +6 -9
  13. package/src/c4/parser.ts +189 -83
  14. package/src/c4/renderer.ts +8 -9
  15. package/src/chart.ts +296 -83
  16. package/src/class/parser.ts +54 -37
  17. package/src/class/renderer.ts +8 -8
  18. package/src/cli.ts +8 -8
  19. package/src/colors.ts +4 -1
  20. package/src/completion.ts +757 -10
  21. package/src/d3.ts +324 -78
  22. package/src/dgmo-router.ts +63 -8
  23. package/src/echarts.ts +735 -241
  24. package/src/er/parser.ts +94 -76
  25. package/src/er/renderer.ts +6 -5
  26. package/src/gantt/parser.ts +144 -69
  27. package/src/gantt/renderer.ts +50 -14
  28. package/src/gantt/types.ts +3 -3
  29. package/src/graph/flowchart-parser.ts +97 -37
  30. package/src/graph/flowchart-renderer.ts +4 -3
  31. package/src/graph/state-parser.ts +50 -31
  32. package/src/graph/state-renderer.ts +4 -3
  33. package/src/index.ts +14 -5
  34. package/src/infra/compute.ts +1 -0
  35. package/src/infra/layout.ts +3 -0
  36. package/src/infra/parser.ts +291 -92
  37. package/src/infra/renderer.ts +172 -30
  38. package/src/infra/types.ts +5 -0
  39. package/src/initiative-status/layout.ts +1 -1
  40. package/src/initiative-status/parser.ts +121 -47
  41. package/src/initiative-status/renderer.ts +42 -23
  42. package/src/initiative-status/types.ts +10 -2
  43. package/src/kanban/parser.ts +60 -37
  44. package/src/kanban/renderer.ts +2 -2
  45. package/src/kanban/types.ts +1 -0
  46. package/src/org/layout.ts +9 -9
  47. package/src/org/parser.ts +39 -40
  48. package/src/org/renderer.ts +5 -6
  49. package/src/org/resolver.ts +26 -19
  50. package/src/render.ts +1 -1
  51. package/src/sequence/parser.ts +304 -95
  52. package/src/sequence/renderer.ts +9 -9
  53. package/src/sitemap/layout.ts +3 -4
  54. package/src/sitemap/parser.ts +57 -49
  55. package/src/sitemap/renderer.ts +6 -7
  56. package/src/utils/arrows.ts +25 -6
  57. package/src/utils/duration.ts +43 -7
  58. package/src/utils/legend-constants.ts +26 -0
  59. package/src/utils/legend-svg.ts +167 -0
  60. package/src/utils/parsing.ts +247 -7
  61. package/src/utils/tag-groups.ts +160 -15
  62. package/src/utils/title-constants.ts +9 -0
@@ -35,8 +35,8 @@ const CARD_RADIUS = 6;
35
35
  const CARD_PADDING_X = 10;
36
36
  const CARD_PADDING_Y = 6;
37
37
  const CARD_STROKE_WIDTH = 1.5;
38
+ import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT } from '../utils/title-constants';
38
39
  const TITLE_HEIGHT = 30;
39
- const TITLE_FONT_SIZE = 18;
40
40
  const COLUMN_HEADER_FONT_SIZE = 13;
41
41
  const CARD_TITLE_FONT_SIZE = 12;
42
42
  const CARD_META_FONT_SIZE = 10;
@@ -235,7 +235,7 @@ export function renderKanban(
235
235
  .attr('x', DIAGRAM_PADDING)
236
236
  .attr('y', DIAGRAM_PADDING + TITLE_FONT_SIZE)
237
237
  .attr('font-size', TITLE_FONT_SIZE)
238
- .attr('font-weight', 'bold')
238
+ .attr('font-weight', TITLE_FONT_WEIGHT)
239
239
  .attr('fill', palette.text)
240
240
  .text(parsed.title);
241
241
  }
@@ -21,6 +21,7 @@ export interface KanbanColumn {
21
21
  name: string;
22
22
  wipLimit?: number;
23
23
  color?: string;
24
+ metadata?: Record<string, string>;
24
25
  cards: KanbanCard[];
25
26
  lineNumber: number;
26
27
  }
package/src/org/layout.ts CHANGED
@@ -3,8 +3,10 @@
3
3
  // ============================================================
4
4
 
5
5
  import { hierarchy, tree } from 'd3-hierarchy';
6
- import type { ParsedOrg, OrgNode, OrgTagGroup } from './parser';
6
+ import type { ParsedOrg, OrgNode } from './parser';
7
+ import type { TagGroup } from '../utils/tag-groups';
7
8
  import { resolveTagColor, injectDefaultTagMetadata } from '../utils/tag-groups';
9
+ import { LEGEND_PILL_FONT_SIZE, LEGEND_ENTRY_FONT_SIZE, measureLegendText } from '../utils/legend-constants';
8
10
 
9
11
  // ============================================================
10
12
  // Types
@@ -101,10 +103,8 @@ const STACK_V_GAP = 20;
101
103
  const LEGEND_GAP = 30;
102
104
  const LEGEND_HEIGHT = 28;
103
105
  const LEGEND_PILL_PAD = 16;
104
- const LEGEND_PILL_FONT_W = 11 * 0.6;
105
106
  const LEGEND_CAPSULE_PAD = 4;
106
107
  const LEGEND_DOT_R = 4;
107
- const LEGEND_ENTRY_FONT_W = 10 * 0.6;
108
108
  const LEGEND_ENTRY_DOT_GAP = 4;
109
109
  const LEGEND_ENTRY_TRAIL = 8;
110
110
  const LEGEND_GROUP_GAP = 12;
@@ -167,7 +167,7 @@ function computeCardHeight(meta: Record<string, string>): number {
167
167
 
168
168
  function resolveNodeColor(
169
169
  node: OrgNode,
170
- tagGroups: OrgTagGroup[],
170
+ tagGroups: TagGroup[],
171
171
  activeGroupName: string | null
172
172
  ): string | undefined {
173
173
  // Explicit inline (color) always wins — handled before tag resolution
@@ -262,7 +262,7 @@ function centerHeavyChildren(node: TreeNode): void {
262
262
  // ============================================================
263
263
 
264
264
  function computeLegendGroups(
265
- tagGroups: OrgTagGroup[],
265
+ tagGroups: TagGroup[],
266
266
  showEyeIcons: boolean,
267
267
  usedValuesByGroup?: Map<string, Set<string>>
268
268
  ): OrgLegendGroup[] {
@@ -279,7 +279,7 @@ function computeLegendGroups(
279
279
  if (visibleEntries.length === 0) continue;
280
280
 
281
281
  // Pill label shows just the group name (alias is for DSL shorthand only)
282
- const pillWidth = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
282
+ const pillWidth = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
283
283
  const minPillWidth = pillWidth;
284
284
 
285
285
  // Capsule: pad + pill + gap + entries + pad
@@ -288,7 +288,7 @@ function computeLegendGroups(
288
288
  entriesWidth +=
289
289
  LEGEND_DOT_R * 2 +
290
290
  LEGEND_ENTRY_DOT_GAP +
291
- entry.value.length * LEGEND_ENTRY_FONT_W +
291
+ measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) +
292
292
  LEGEND_ENTRY_TRAIL;
293
293
  }
294
294
  const eyeSpace = showEyeIcons ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
@@ -320,7 +320,7 @@ function computeLegendGroups(
320
320
  */
321
321
  function injectDefaultMetadata(
322
322
  roots: OrgNode[],
323
- tagGroups: OrgTagGroup[]
323
+ tagGroups: TagGroup[]
324
324
  ): void {
325
325
  // Flatten all nodes (recursive) for the shared utility
326
326
  const allNodes: OrgNode[] = [];
@@ -377,7 +377,7 @@ export function layoutOrg(
377
377
 
378
378
  // Build tree structure
379
379
  const subNodeLabel = parsed.options['sub-node-label'] ?? undefined;
380
- const showSubNodeCount = parsed.options['show-sub-node-count']?.toLowerCase() === 'yes';
380
+ const showSubNodeCount = ['yes', 'on'].includes(parsed.options['show-sub-node-count']?.toLowerCase() ?? '');
381
381
  const treeNodes = buildTreeNodes(parsed.roots, hiddenCounts, hiddenAttributes, subNodeLabel, showSubNodeCount);
382
382
 
383
383
  // Single root or virtual root for multiple roots
package/src/org/parser.ts CHANGED
@@ -8,20 +8,14 @@ import {
8
8
  extractColor,
9
9
  parsePipeMetadata,
10
10
  MULTIPLE_PIPE_WARNING,
11
- CHART_TYPE_RE,
12
- TITLE_RE,
13
- OPTION_RE,
11
+ parseFirstLine,
12
+ OPTION_NOCOLON_RE,
14
13
  } from '../utils/parsing';
15
14
 
16
15
  // ============================================================
17
16
  // Types
18
17
  // ============================================================
19
18
 
20
- /** @deprecated Use `TagEntry` from `utils/tag-groups` */
21
- export type OrgTagEntry = TagEntry;
22
- /** @deprecated Use `TagGroup` from `utils/tag-groups` */
23
- export type OrgTagGroup = TagGroup;
24
-
25
19
  export interface OrgNode {
26
20
  id: string;
27
21
  label: string;
@@ -37,7 +31,7 @@ export interface ParsedOrg {
37
31
  title: string | null;
38
32
  titleLineNumber: number | null;
39
33
  roots: OrgNode[];
40
- tagGroups: OrgTagGroup[];
34
+ tagGroups: TagGroup[];
41
35
  options: Record<string, string>;
42
36
  diagnostics: DgmoError[];
43
37
  error: string | null;
@@ -50,6 +44,15 @@ export interface ParsedOrg {
50
44
  const CONTAINER_RE = /^\[([^\]]+)\]$/;
51
45
  const METADATA_RE = /^([^:]+):\s*(.+)$/;
52
46
 
47
+ /** Known org chart options (key-value). */
48
+ const KNOWN_OPTIONS = new Set([
49
+ 'direction', 'sub-node-label', 'hide', 'show-sub-node-count',
50
+ ]);
51
+ /** Known org chart boolean options (bare keyword = on). */
52
+ const KNOWN_BOOLEANS = new Set([
53
+ 'show-sub-node-count',
54
+ ]);
55
+
53
56
  // ============================================================
54
57
  // Inference
55
58
  // ============================================================
@@ -111,7 +114,7 @@ export function parseOrg(
111
114
  let containerCounter = 0;
112
115
 
113
116
  // Tag group parsing state
114
- let currentTagGroup: OrgTagGroup | null = null;
117
+ let currentTagGroup: TagGroup | null = null;
115
118
 
116
119
  // Alias map: alias (lowercased) → group name (lowercased)
117
120
  const aliasMap = new Map<string, string>();
@@ -139,28 +142,21 @@ export function parseOrg(
139
142
 
140
143
  // --- Header phase ---
141
144
 
142
- // chart: type
145
+ // Extract chart type + title from first line (e.g. `org My Org Chart`)
143
146
  if (!contentStarted) {
144
- const chartMatch = trimmed.match(CHART_TYPE_RE);
145
- if (chartMatch) {
146
- const chartType = chartMatch[1].trim().toLowerCase();
147
- if (chartType !== 'org') {
147
+ const firstLine = parseFirstLine(trimmed);
148
+ if (firstLine) {
149
+ if (firstLine.chartType !== 'org') {
148
150
  const allTypes = ['org', 'class', 'flowchart', 'sequence', 'er', 'bar', 'line', 'pie', 'scatter', 'sankey', 'venn', 'timeline', 'arc', 'slope'];
149
- let msg = `Expected chart type "org", got "${chartType}"`;
150
- const hint = suggest(chartType, allTypes);
151
+ let msg = `Expected chart type "org", got "${firstLine.chartType}"`;
152
+ const hint = suggest(firstLine.chartType, allTypes);
151
153
  if (hint) msg += `. ${hint}`;
152
154
  return fail(lineNumber, msg);
153
155
  }
154
- continue;
155
- }
156
- }
157
-
158
- // title: value
159
- if (!contentStarted) {
160
- const titleMatch = trimmed.match(TITLE_RE);
161
- if (titleMatch) {
162
- result.title = titleMatch[1].trim();
163
- result.titleLineNumber = lineNumber;
156
+ if (firstLine.title) {
157
+ result.title = firstLine.title;
158
+ result.titleLineNumber = lineNumber;
159
+ }
164
160
  continue;
165
161
  }
166
162
  }
@@ -174,7 +170,8 @@ export function parseOrg(
174
170
  continue;
175
171
  }
176
172
  if (tagBlockMatch.deprecated) {
177
- pushWarning(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups — use 'tag: ${tagBlockMatch.name}' instead`);
173
+ pushError(lineNumber, `'## ${tagBlockMatch.name}' is no longer supported — use 'tag: ${tagBlockMatch.name}' instead`);
174
+ continue;
178
175
  }
179
176
  currentTagGroup = {
180
177
  name: tagBlockMatch.name,
@@ -189,34 +186,36 @@ export function parseOrg(
189
186
  continue;
190
187
  }
191
188
 
192
- // Generic header options (key: value lines before content/tag groups)
193
- // Only match non-indented lines with simple hyphenated keys
189
+ // Generic header options (space-separated: `key value` or bare boolean `key`)
190
+ // Only match non-indented lines with known option keys
194
191
  if (!contentStarted && !currentTagGroup && measureIndent(line) === 0) {
195
- const optMatch = trimmed.match(OPTION_RE);
192
+ const optMatch = trimmed.match(OPTION_NOCOLON_RE);
196
193
  if (optMatch) {
197
194
  const key = optMatch[1].trim().toLowerCase();
198
- if (key !== 'chart' && key !== 'title') {
195
+ if (KNOWN_OPTIONS.has(key)) {
199
196
  result.options[key] = optMatch[2].trim();
200
197
  continue;
201
198
  }
202
199
  }
200
+ // Bare boolean option (single keyword, no value)
201
+ if (KNOWN_BOOLEANS.has(trimmed.toLowerCase())) {
202
+ result.options[trimmed.toLowerCase()] = 'on';
203
+ continue;
204
+ }
203
205
  }
204
206
 
205
- // Tag group entries (indented Value(color) [default] under ## heading)
207
+ // Tag group entries (indented Value(color) under tag heading)
208
+ // First entry is implicitly the default.
206
209
  if (currentTagGroup && !contentStarted) {
207
210
  const indent = measureIndent(line);
208
211
  if (indent > 0) {
209
- // Strip trailing `default` keyword before extracting color
210
- const isDefault = /\bdefault\s*$/.test(trimmed);
211
- const entryText = isDefault
212
- ? trimmed.replace(/\s+default\s*$/, '').trim()
213
- : trimmed;
214
- const { label, color } = extractColor(entryText, palette);
212
+ const { label, color } = extractColor(trimmed, palette);
215
213
  if (!color) {
216
214
  pushError(lineNumber, `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`);
217
215
  continue;
218
216
  }
219
- if (isDefault) {
217
+ // First entry is the default
218
+ if (currentTagGroup.entries.length === 0) {
220
219
  currentTagGroup.defaultValue = label;
221
220
  }
222
221
  currentTagGroup.entries.push({
@@ -15,11 +15,9 @@ import {
15
15
  LEGEND_HEIGHT,
16
16
  LEGEND_PILL_PAD,
17
17
  LEGEND_PILL_FONT_SIZE,
18
- LEGEND_PILL_FONT_W,
19
18
  LEGEND_CAPSULE_PAD,
20
19
  LEGEND_DOT_R,
21
20
  LEGEND_ENTRY_FONT_SIZE,
22
- LEGEND_ENTRY_FONT_W,
23
21
  LEGEND_ENTRY_DOT_GAP,
24
22
  LEGEND_ENTRY_TRAIL,
25
23
  LEGEND_GROUP_GAP,
@@ -27,6 +25,7 @@ import {
27
25
  LEGEND_EYE_GAP,
28
26
  EYE_OPEN_PATH,
29
27
  EYE_CLOSED_PATH,
28
+ measureLegendText,
30
29
  } from '../utils/legend-constants';
31
30
 
32
31
  // ============================================================
@@ -35,8 +34,8 @@ import {
35
34
 
36
35
  const DIAGRAM_PADDING = 20;
37
36
  const MAX_SCALE = 3;
37
+ import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT } from '../utils/title-constants';
38
38
  const TITLE_HEIGHT = 30;
39
- const TITLE_FONT_SIZE = 18;
40
39
  const LABEL_FONT_SIZE = 13;
41
40
  const META_FONT_SIZE = 11;
42
41
  const META_LINE_HEIGHT = 16;
@@ -179,7 +178,7 @@ export function renderOrg(
179
178
  .attr('text-anchor', 'middle')
180
179
  .attr('fill', palette.text)
181
180
  .attr('font-size', TITLE_FONT_SIZE)
182
- .attr('font-weight', 'bold')
181
+ .attr('font-weight', TITLE_FONT_WEIGHT)
183
182
  .attr('class', 'org-title chart-title')
184
183
  .style(
185
184
  'cursor',
@@ -534,7 +533,7 @@ export function renderOrg(
534
533
 
535
534
  const pillLabel = group.name;
536
535
  const pillWidth =
537
- pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
536
+ measureLegendText(pillLabel, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
538
537
 
539
538
  const gX = fixedPositions?.get(group.name) ?? group.x;
540
539
  const gY = fixedPositions ? 0 : group.y;
@@ -656,7 +655,7 @@ export function renderOrg(
656
655
  .attr('fill', palette.textMuted)
657
656
  .text(entryLabel);
658
657
 
659
- entryX = textX + entryLabel.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
658
+ entryX = textX + measureLegendText(entryLabel, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
660
659
  }
661
660
  }
662
661
  }
@@ -34,10 +34,18 @@ export interface ResolveImportsResult {
34
34
  // ============================================================
35
35
 
36
36
  const MAX_DEPTH = 10;
37
- const IMPORT_RE = /^(\s+)import:\s+(.+\.dgmo)\s*$/i;
38
- const TAGS_RE = /^tags:\s+(.+\.dgmo)\s*$/i;
39
- const HEADER_RE = /^(chart|title)\s*:/i;
40
- const OPTION_RE = /^[a-z][a-z0-9-]*\s*:/i;
37
+ const IMPORT_RE = /^(\s+)import:?\s+(.+\.dgmo)\s*$/i;
38
+ const TAGS_RE = /^tags:?\s+(.+\.dgmo)\s*$/i;
39
+ /** Matches new-style first line: `org ...` or old `chart: ...` or `title: ...` */
40
+ const HEADER_RE = /^(org|kanban|chart\s*:|title\s*:)/i;
41
+ /**
42
+ * Known option keys that can appear in org chart headers (space-separated).
43
+ * Only these are stripped from imported files — avoids eating content like "Alice Chen".
44
+ */
45
+ const KNOWN_HEADER_OPTIONS = new Set([
46
+ 'direction', 'sub-node-label', 'hide', 'show-sub-node-count',
47
+ 'color-off',
48
+ ]);
41
49
 
42
50
  // ============================================================
43
51
  // Path Helpers (pure string ops — no Node `path` dependency)
@@ -116,7 +124,7 @@ interface ParsedHeader {
116
124
 
117
125
  /**
118
126
  * Separate an imported file into header (stripped) and content body.
119
- * Also extracts tag groups and tags: directive for merging.
127
+ * Also extracts tag groups and tags directive for merging.
120
128
  */
121
129
  function parseFileHeader(lines: string[]): ParsedHeader {
122
130
  const tagGroups = extractTagGroups(lines);
@@ -157,11 +165,10 @@ function parseFileHeader(lines: string[]): ParsedHeader {
157
165
  continue;
158
166
  }
159
167
 
160
- // Other option-like header lines (non-indented key: value)
161
- if (OPTION_RE.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/)) {
162
- // Check it's not a content line (node with metadata)
163
- const key = trimmed.split(':')[0].trim().toLowerCase();
164
- if (key !== 'chart' && key !== 'title' && !trimmed.includes('|')) {
168
+ // Known option header lines (space-separated `key value` or bare boolean)
169
+ if (!lines[i].match(/^\s/) && !isTagBlockHeading(trimmed) && !trimmed.includes('|')) {
170
+ const firstToken = trimmed.split(/\s/)[0].toLowerCase();
171
+ if (KNOWN_HEADER_OPTIONS.has(firstToken)) {
165
172
  continue;
166
173
  }
167
174
  }
@@ -181,7 +188,7 @@ function parseFileHeader(lines: string[]): ParsedHeader {
181
188
  // ============================================================
182
189
 
183
190
  /**
184
- * Pre-processes org chart content, resolving `tags:` and `import:` directives.
191
+ * Pre-processes org chart content, resolving `tags` and `import` directives.
185
192
  *
186
193
  * @param content - Raw .dgmo file content
187
194
  * @param filePath - Absolute path of the file (for relative path resolution)
@@ -220,7 +227,7 @@ async function resolveFile(
220
227
  const bodyStartIndex = findBodyStart(lines);
221
228
 
222
229
  // Collect header lines (chart:, title:, options, tags:)
223
- let tagsLineNumber = 0; // 1-based line number of the tags: directive
230
+ let tagsLineNumber = 0; // 1-based line number of the tags directive
224
231
  for (let i = 0; i < bodyStartIndex; i++) {
225
232
  const trimmed = lines[i].trim();
226
233
  if (trimmed === '' || trimmed.startsWith('//')) {
@@ -240,7 +247,7 @@ async function resolveFile(
240
247
  headerLines.push({ text: lines[i], originalLine: i + 1 });
241
248
  }
242
249
 
243
- // ---- Step 2: Resolve tags: directive ----
250
+ // ---- Step 2: Resolve tags directive ----
244
251
  let tagsFileGroups: TagGroupBlock[] = [];
245
252
  if (tagsDirective) {
246
253
  const tagsPath = resolvePath(filePath, tagsDirective);
@@ -255,7 +262,7 @@ async function resolveFile(
255
262
  }
256
263
  }
257
264
 
258
- // ---- Step 3: Resolve import: directives in body ----
265
+ // ---- Step 3: Resolve import directives in body ----
259
266
  const bodyLines = lines.slice(bodyStartIndex);
260
267
  const resolvedBodyLines: { text: string; originalLine: number | null; importSource: ImportSource | null }[] = [];
261
268
  const importedTagGroups: TagGroupBlock[] = [];
@@ -381,7 +388,7 @@ async function resolveFile(
381
388
  const lineMap: (number | null)[] = [null];
382
389
  const importSourceMap: (ImportSource | null)[] = [null];
383
390
 
384
- // Header lines (chart:, title:, options — no tags: or tag groups)
391
+ // Header lines (chart:, title:, options — no tags or tag groups)
385
392
  for (const entry of headerLines) {
386
393
  outputLines.push(entry.text);
387
394
  lineMap.push(entry.originalLine);
@@ -470,10 +477,10 @@ function findBodyStart(lines: string[]): number {
470
477
  if (HEADER_RE.test(trimmed)) continue;
471
478
  if (TAGS_RE.test(trimmed)) continue;
472
479
 
473
- // Option-like lines (non-indented key: value before content)
474
- if (OPTION_RE.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes('|')) {
475
- const key = trimmed.split(':')[0].trim().toLowerCase();
476
- if (key !== 'chart' && key !== 'title') {
480
+ // Known option lines (space-separated `key value` or bare boolean before content)
481
+ if (!lines[i].match(/^\s/) && !trimmed.includes('|') && !isTagBlockHeading(trimmed)) {
482
+ const firstToken = trimmed.split(/\s/)[0].toLowerCase();
483
+ if (KNOWN_HEADER_OPTIONS.has(firstToken)) {
477
484
  continue;
478
485
  }
479
486
  }
package/src/render.ts CHANGED
@@ -57,7 +57,7 @@ export async function render(
57
57
  ): Promise<string> {
58
58
  const theme = options?.theme ?? 'light';
59
59
  const paletteName = options?.palette ?? 'nord';
60
- const branding = options?.branding ?? true;
60
+ const branding = options?.branding ?? false;
61
61
 
62
62
  const paletteColors = getPalette(paletteName)[theme === 'dark' ? 'dark' : 'light'];
63
63