@markdy/core 0.8.7 → 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
  }
@@ -942,6 +989,12 @@ function splitTargetLabel(token, lineNo) {
942
989
  function parseCueLine(line, lineNo) {
943
990
  const trimmed = line.trim();
944
991
  const props = parseProps(trimmed);
992
+ if (/^@\+?\d/.test(trimmed) || /^\w[\w.-]*\.\w+\(/.test(trimmed) || /^camera\./.test(trimmed)) {
993
+ throw new ParseError(
994
+ "unsupported timeline command; use beat cues like show, frame, focus, glow, and flow lines",
995
+ lineNo
996
+ );
997
+ }
945
998
  const parallelParts = splitOutsideQuotes(trimmed, "&");
946
999
  if (parallelParts.length > 1) {
947
1000
  return {
@@ -960,7 +1013,8 @@ function parseCueLine(line, lineNo) {
960
1013
  };
961
1014
  }
962
1015
  const [head, ...rest] = trimmed.split(/\s+/);
963
- const keyword = head.toLowerCase();
1016
+ const rawKeyword = head.toLowerCase();
1017
+ const keyword = CUE_ALIASES[rawKeyword] ?? rawKeyword;
964
1018
  if (keyword === "show" || keyword === "hide") {
965
1019
  const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
966
1020
  return {
@@ -1023,7 +1077,7 @@ function parseCueLine(line, lineNo) {
1023
1077
  }
1024
1078
  return { kind: "use", pattern: m[1], args, line: lineNo };
1025
1079
  }
1026
- throw new ParseError(`unknown cue '${head}'`, lineNo);
1080
+ throw new ParseError(`unknown cue '${head}'; use show, hide, glow, focus, frame, or use`, lineNo);
1027
1081
  }
1028
1082
  function expandPatternCues(cues, patterns, line) {
1029
1083
  const out = [];
@@ -1121,6 +1175,50 @@ function normalizeCueBlocks(blocks) {
1121
1175
  }
1122
1176
  return normalized;
1123
1177
  }
1178
+ function unsupportedSyntaxMessage(line) {
1179
+ if (/^actor\s+/.test(line) || /\bfigure\s*\(/.test(line) || /\bbox\s*\(/.test(line) || /\bat\s*\(/.test(line)) {
1180
+ return "unsupported manual drawing syntax; declare architecture nodes like service API, cache Redis, and database DB";
1181
+ }
1182
+ if (/^@\+?\d/.test(line)) {
1183
+ return "unsupported timeline command; put flow and cue lines inside beat blocks";
1184
+ }
1185
+ if (/^camera\b/.test(line)) {
1186
+ return "unsupported camera command; use frame NodeOrGroup zoom=... inside a beat";
1187
+ }
1188
+ return null;
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
+ }
1124
1222
  function pushWarning(diagnostics, seen, line, message) {
1125
1223
  const key = `${line}:${message}`;
1126
1224
  if (seen.has(key)) return;
@@ -1200,16 +1298,23 @@ function parse(source, opts = {}) {
1200
1298
  const beats = [];
1201
1299
  let title = "";
1202
1300
  let edgeCounter = 0;
1203
- 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);
1204
1304
  let i = 0;
1205
1305
  while (i < blocks.length) {
1206
1306
  const block = blocks[i];
1207
1307
  const line = block.text;
1208
1308
  const lineNo = block.line;
1309
+ const unsupportedMessage = unsupportedSyntaxMessage(line);
1310
+ if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
1209
1311
  if (line.startsWith("scene")) {
1210
- const rest = line.slice(5).trim();
1211
- let remainder = rest;
1212
- const str = parseStringToken(rest);
1312
+ const rest2 = line.slice(5).trim();
1313
+ if (line.endsWith("{")) {
1314
+ throw new ParseError(`nested scene blocks are not supported; use one scene with multiple beat blocks`, lineNo);
1315
+ }
1316
+ let remainder = rest2;
1317
+ const str = parseStringToken(rest2);
1213
1318
  if (str) {
1214
1319
  title = str.value;
1215
1320
  remainder = str.rest;
@@ -1256,16 +1361,31 @@ function parse(source, opts = {}) {
1256
1361
  continue;
1257
1362
  }
1258
1363
  if (line.startsWith("group ")) {
1259
- const m = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*(.+)$/);
1260
- if (!m) throw new ParseError(`expected group name: A B C`, lineNo);
1261
- groups[m[1]] = {
1262
- id: m[1],
1263
- label: m[2],
1264
- 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,
1265
1385
  props: {},
1266
1386
  line: lineNo
1267
1387
  };
1268
- i++;
1388
+ i = nextIdx;
1269
1389
  continue;
1270
1390
  }
1271
1391
  if (line.startsWith("edge ")) {
@@ -1287,7 +1407,7 @@ function parse(source, opts = {}) {
1287
1407
  continue;
1288
1408
  }
1289
1409
  if (line.startsWith("beat ")) {
1290
- const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
1410
+ const m = line.match(/^beat\s+([\w.-]+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
1291
1411
  if (!m) throw new ParseError(`expected beat name:`, lineNo);
1292
1412
  const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
1293
1413
  let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
@@ -1358,6 +1478,7 @@ function parseAndCompile(source) {
1358
1478
  }
1359
1479
  export {
1360
1480
  BEAT_CUE_KEYWORDS,
1481
+ CUE_ALIASES,
1361
1482
  EDGE_OPERATORS,
1362
1483
  NODE_ALIASES,
1363
1484
  NODE_KINDS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "0.8.7",
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",