@markdy/core 0.8.8 → 0.8.9

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/index.d.ts CHANGED
@@ -222,6 +222,8 @@ declare function resolveTheme(name: string): ThemeTokens;
222
222
 
223
223
  declare const NODE_KINDS: Set<string>;
224
224
  declare const EDGE_OPERATORS: Record<string, "request" | "response" | "event" | "dependency">;
225
+ /** Natural-language cue synonyms that AIs reach for, mapped to real cues. */
226
+ declare const CUE_ALIASES: Record<string, string>;
225
227
  declare const BEAT_CUE_KEYWORDS: Set<string>;
226
228
  declare const SCENE_KEYS: Set<string>;
227
229
  declare function nodeRole(kind: string): string;
@@ -234,4 +236,4 @@ declare const TECHNICAL_NODE_TYPES: readonly ["service", "api", "microservice",
234
236
  declare const VISUAL_PRIMITIVE_TYPES: readonly ["panel", "surface", "terminal", "metric", "stat", "grid", "matrix", "lane", "track", "marker", "dot", "token_strip", "chips", "glyph_card", "glyph"];
235
237
  declare const TECHNICAL_NODE_KINDS: Record<(typeof TECHNICAL_NODE_TYPES)[number], string>;
236
238
 
237
- export { BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Cue, type Diagnostic, type DiagramAST, EDGE_OPERATORS, type EdgeDecl, type EdgeKind, type FlowSegment, type GroupDecl, type LayoutDirection, NODE_ALIASES, NODE_KINDS, type NodeDecl, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PositionedNode, type RenderPlan, type RoutedEdge, SCENE_KEYS, type SceneMeta, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeTokens, type TimedCue, VISUAL_PRIMITIVE_TYPES, canonicalNodeKind, compile, compilePlan, humanizeId, nodeRole, parse, parseAndCompile, resolveTheme };
239
+ export { BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, CUE_ALIASES, type Cue, type Diagnostic, type DiagramAST, EDGE_OPERATORS, type EdgeDecl, type EdgeKind, type FlowSegment, type GroupDecl, type LayoutDirection, NODE_ALIASES, NODE_KINDS, type NodeDecl, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PositionedNode, type RenderPlan, type RoutedEdge, SCENE_KEYS, type SceneMeta, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeTokens, type TimedCue, VISUAL_PRIMITIVE_TYPES, canonicalNodeKind, compile, compilePlan, humanizeId, nodeRole, parse, parseAndCompile, resolveTheme };
package/dist/index.js CHANGED
@@ -350,7 +350,20 @@ var EDGE_OPERATORS = {
350
350
  "--": "dependency"
351
351
  };
352
352
  var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
353
- var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "frame", "use"]);
353
+ var CUE_ALIASES = {
354
+ pulse: "focus",
355
+ highlight: "glow",
356
+ emphasize: "glow"
357
+ };
358
+ var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set([
359
+ "show",
360
+ "hide",
361
+ "glow",
362
+ "focus",
363
+ "frame",
364
+ "use",
365
+ ...Object.keys(CUE_ALIASES)
366
+ ]);
354
367
  var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction", "layout"]);
355
368
  function nodeRole(kind) {
356
369
  return TECHNICAL_NODE_KINDS[kind] ?? "compute";
@@ -777,7 +790,9 @@ function stripComment(line) {
777
790
  }
778
791
  if (inString) continue;
779
792
  if (ch === "/" && line[i + 1] === "/") return line.slice(0, i);
780
- if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i);
793
+ if (ch === "#" && (i === 0 || /\s/.test(line[i - 1])) && (i + 1 >= line.length || /\s/.test(line[i + 1]))) {
794
+ return line.slice(0, i);
795
+ }
781
796
  }
782
797
  return line;
783
798
  }
@@ -905,9 +920,41 @@ function stripCueProps(raw, keys) {
905
920
  }
906
921
  return raw.trim();
907
922
  }
923
+ function tokenizeFlowChain(line) {
924
+ const parts = [];
925
+ let current = "";
926
+ let inString = false;
927
+ let escaped = false;
928
+ for (let i = 0; i < line.length; i++) {
929
+ const ch = line[i];
930
+ if (inString) {
931
+ current += ch;
932
+ if (escaped) escaped = false;
933
+ else if (ch === "\\") escaped = true;
934
+ else if (ch === '"') inString = false;
935
+ continue;
936
+ }
937
+ if (ch === '"') {
938
+ inString = true;
939
+ current += ch;
940
+ continue;
941
+ }
942
+ const op = line.slice(i, i + 2);
943
+ if (op === "->" || op === "<-" || op === "~>" || op === "--") {
944
+ if (current.trim()) parts.push(current.trim());
945
+ parts.push(op);
946
+ current = "";
947
+ i += 1;
948
+ continue;
949
+ }
950
+ current += ch;
951
+ }
952
+ if (current.trim()) parts.push(current.trim());
953
+ return parts;
954
+ }
908
955
  function parseFlowChain(line, lineNo) {
909
956
  const segments = [];
910
- const parts = line.split(FLOW_OP_RE).map((p) => p.trim()).filter(Boolean);
957
+ const parts = tokenizeFlowChain(line);
911
958
  if (parts.length < 3) {
912
959
  throw new ParseError(`expected flow chain like A -> B "label"`, lineNo);
913
960
  }
@@ -966,7 +1013,8 @@ function parseCueLine(line, lineNo) {
966
1013
  };
967
1014
  }
968
1015
  const [head, ...rest] = trimmed.split(/\s+/);
969
- const keyword = head.toLowerCase();
1016
+ const rawKeyword = head.toLowerCase();
1017
+ const keyword = CUE_ALIASES[rawKeyword] ?? rawKeyword;
970
1018
  if (keyword === "show" || keyword === "hide") {
971
1019
  const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
972
1020
  return {
@@ -1029,7 +1077,7 @@ function parseCueLine(line, lineNo) {
1029
1077
  }
1030
1078
  return { kind: "use", pattern: m[1], args, line: lineNo };
1031
1079
  }
1032
- throw new ParseError(`unknown cue '${head}'`, lineNo);
1080
+ throw new ParseError(`unknown cue '${head}'; use show, hide, glow, focus, frame, or use`, lineNo);
1033
1081
  }
1034
1082
  function expandPatternCues(cues, patterns, line) {
1035
1083
  const out = [];
@@ -1128,20 +1176,49 @@ function normalizeCueBlocks(blocks) {
1128
1176
  return normalized;
1129
1177
  }
1130
1178
  function unsupportedSyntaxMessage(line) {
1131
- if (/^var\s+/.test(line)) {
1132
- return "unsupported variable declaration; use scene properties and style declarations instead";
1133
- }
1134
1179
  if (/^actor\s+/.test(line) || /\bfigure\s*\(/.test(line) || /\bbox\s*\(/.test(line) || /\bat\s*\(/.test(line)) {
1135
1180
  return "unsupported manual drawing syntax; declare architecture nodes like service API, cache Redis, and database DB";
1136
1181
  }
1137
1182
  if (/^@\+?\d/.test(line)) {
1138
1183
  return "unsupported timeline command; put flow and cue lines inside beat blocks";
1139
1184
  }
1140
- if (/^camera\./.test(line)) {
1185
+ if (/^camera\b/.test(line)) {
1141
1186
  return "unsupported camera command; use frame NodeOrGroup zoom=... inside a beat";
1142
1187
  }
1143
1188
  return null;
1144
1189
  }
1190
+ var RESERVED_VAR_NAMES = /* @__PURE__ */ new Set(["nodes", "title", "edges"]);
1191
+ function extractVars(blocks, diagnostics) {
1192
+ const vars = /* @__PURE__ */ new Map();
1193
+ const rest = [];
1194
+ for (const block of blocks) {
1195
+ if (!/^var\b/.test(block.text)) {
1196
+ rest.push(block);
1197
+ continue;
1198
+ }
1199
+ const m = block.text.match(/^var\s+([A-Za-z_]\w*)\s*=\s*(.+)$/);
1200
+ if (!m) throw new ParseError("expected var name = value", block.line);
1201
+ const name = m[1];
1202
+ if (RESERVED_VAR_NAMES.has(name)) {
1203
+ diagnostics.push({ severity: "warning", message: `var '${name}' shadows a reserved selector and was ignored`, line: block.line });
1204
+ continue;
1205
+ }
1206
+ const raw = m[2].trim();
1207
+ const str = parseStringToken(raw);
1208
+ vars.set(name, str && str.rest === "" ? str.value : raw);
1209
+ }
1210
+ return { vars, rest };
1211
+ }
1212
+ function applyVars(blocks, vars) {
1213
+ if (vars.size === 0) return blocks;
1214
+ return blocks.map((block) => {
1215
+ let text = block.text;
1216
+ for (const [name, value] of vars) {
1217
+ text = text.replace(new RegExp(`\\$${name}(?![\\w])`, "g"), value);
1218
+ }
1219
+ return { ...block, text };
1220
+ });
1221
+ }
1145
1222
  function pushWarning(diagnostics, seen, line, message) {
1146
1223
  const key = `${line}:${message}`;
1147
1224
  if (seen.has(key)) return;
@@ -1221,7 +1298,9 @@ function parse(source, opts = {}) {
1221
1298
  const beats = [];
1222
1299
  let title = "";
1223
1300
  let edgeCounter = 0;
1224
- const blocks = readBlocks(lines.map(stripComment));
1301
+ const rawBlocks = readBlocks(lines.map(stripComment));
1302
+ const { vars, rest } = extractVars(rawBlocks, diagnostics);
1303
+ const blocks = applyVars(rest, vars);
1225
1304
  let i = 0;
1226
1305
  while (i < blocks.length) {
1227
1306
  const block = blocks[i];
@@ -1230,12 +1309,12 @@ function parse(source, opts = {}) {
1230
1309
  const unsupportedMessage = unsupportedSyntaxMessage(line);
1231
1310
  if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
1232
1311
  if (line.startsWith("scene")) {
1233
- const rest = line.slice(5).trim();
1312
+ const rest2 = line.slice(5).trim();
1234
1313
  if (line.endsWith("{")) {
1235
1314
  throw new ParseError(`nested scene blocks are not supported; use one scene with multiple beat blocks`, lineNo);
1236
1315
  }
1237
- let remainder = rest;
1238
- const str = parseStringToken(rest);
1316
+ let remainder = rest2;
1317
+ const str = parseStringToken(rest2);
1239
1318
  if (str) {
1240
1319
  title = str.value;
1241
1320
  remainder = str.rest;
@@ -1282,16 +1361,31 @@ function parse(source, opts = {}) {
1282
1361
  continue;
1283
1362
  }
1284
1363
  if (line.startsWith("group ")) {
1285
- const m = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*(.+)$/);
1286
- if (!m) throw new ParseError(`expected group name: A B C`, lineNo);
1287
- groups[m[1]] = {
1288
- id: m[1],
1289
- label: m[2],
1290
- members: splitTargets(m[3]),
1364
+ const inline = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*(.+)$/);
1365
+ if (inline) {
1366
+ groups[inline[1]] = {
1367
+ id: inline[1],
1368
+ label: inline[2],
1369
+ members: splitTargets(inline[3]),
1370
+ props: {},
1371
+ line: lineNo
1372
+ };
1373
+ i++;
1374
+ continue;
1375
+ }
1376
+ const header = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*$/);
1377
+ if (!header) throw new ParseError(`expected group name: A B C`, lineNo);
1378
+ const { body, nextIdx } = readIndentedBody(blocks, i + 1, block.indent);
1379
+ const members = body.flatMap((b) => splitTargets(b.text));
1380
+ if (members.length === 0) throw new ParseError(`group '${header[1]}' has no members`, lineNo);
1381
+ groups[header[1]] = {
1382
+ id: header[1],
1383
+ label: header[2],
1384
+ members,
1291
1385
  props: {},
1292
1386
  line: lineNo
1293
1387
  };
1294
- i++;
1388
+ i = nextIdx;
1295
1389
  continue;
1296
1390
  }
1297
1391
  if (line.startsWith("edge ")) {
@@ -1313,7 +1407,7 @@ function parse(source, opts = {}) {
1313
1407
  continue;
1314
1408
  }
1315
1409
  if (line.startsWith("beat ")) {
1316
- const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
1410
+ const m = line.match(/^beat\s+([\w.-]+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
1317
1411
  if (!m) throw new ParseError(`expected beat name:`, lineNo);
1318
1412
  const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
1319
1413
  let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
@@ -1384,6 +1478,7 @@ function parseAndCompile(source) {
1384
1478
  }
1385
1479
  export {
1386
1480
  BEAT_CUE_KEYWORDS,
1481
+ CUE_ALIASES,
1387
1482
  EDGE_OPERATORS,
1388
1483
  NODE_ALIASES,
1389
1484
  NODE_KINDS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "MarkdyScript parser and diagram compiler (auto-layout, edge routing, cue scheduling) — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",