@markdy/core 1.0.9 → 1.0.11

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/README.md CHANGED
@@ -37,7 +37,7 @@ import { parse, ParseError } from "@markdy/core";
37
37
  import type { DiagramAST } from "@markdy/core";
38
38
 
39
39
  const source = `
40
- scene "Request" theme=paper
40
+ scene theme=paper
41
41
  browser Web
42
42
  service API
43
43
  beat main:
package/dist/index.d.ts CHANGED
@@ -6,6 +6,65 @@ type LayoutDirection = "LR" | "RL" | "TB" | "BT";
6
6
  type EdgeKind = "request" | "response" | "event" | "dependency";
7
7
  type DiagramType = "architecture" | "flowchart" | "tree" | "state" | "sequence" | "constellation" | "loop" | "flywheel" | "medallion" | "quadrant" | "swimlane" | "pyramid" | "radar" | "timeline" | "gantt" | "venn" | "layers" | "nested";
8
8
  type NodeShape = "card" | "rounded" | "diamond" | "circle" | "pill" | "terminal" | "container";
9
+ type PlayerProgress = "none" | "bar" | "boundary";
10
+ /** When and how fast the timeline runs. */
11
+ type PlayerPlaybackConfig = {
12
+ autoplay?: boolean;
13
+ loop?: boolean;
14
+ rate?: number;
15
+ };
16
+ /** Which toolbar affordances are mounted. Declaring the group opts in. */
17
+ type PlayerControlsConfig = {
18
+ play?: boolean;
19
+ restart?: boolean;
20
+ prevBeat?: boolean;
21
+ nextBeat?: boolean;
22
+ seek?: boolean;
23
+ speed?: boolean;
24
+ fit?: boolean;
25
+ resetView?: boolean;
26
+ svg?: boolean;
27
+ share?: boolean;
28
+ /** Speed multipliers offered by the speed buttons. */
29
+ speeds?: number[];
30
+ };
31
+ /** What pointer and key input do. Declaring the group opts in. */
32
+ type PlayerInteractionConfig = {
33
+ zoom?: boolean;
34
+ pan?: boolean;
35
+ clickToPlay?: boolean;
36
+ doubleClickToReset?: boolean;
37
+ /** Window-level shortcuts; opt-in because they capture space and arrows. */
38
+ keyboard?: boolean;
39
+ };
40
+ /** Non-interactive decoration drawn around the scene. */
41
+ type PlayerChromeConfig = {
42
+ badge?: boolean;
43
+ progress?: PlayerProgress;
44
+ progressColor?: string;
45
+ };
46
+ type PlayerConfig = {
47
+ playback?: PlayerPlaybackConfig;
48
+ controls?: PlayerControlsConfig;
49
+ interaction?: PlayerInteractionConfig;
50
+ chrome?: PlayerChromeConfig;
51
+ };
52
+ /** Fully defaulted player behavior produced by `resolvePlayer`. `enabled` is
53
+ * derived: a group is on when at least one of its affordances is on. */
54
+ type ResolvedPlayer = {
55
+ playback: Required<PlayerPlaybackConfig>;
56
+ controls: Required<PlayerControlsConfig> & {
57
+ enabled: boolean;
58
+ };
59
+ interaction: Required<PlayerInteractionConfig> & {
60
+ enabled: boolean;
61
+ };
62
+ chrome: {
63
+ badge: boolean;
64
+ progress: PlayerProgress;
65
+ progressColor?: string;
66
+ };
67
+ };
9
68
  type SceneMeta = {
10
69
  title?: string;
11
70
  width: number;
@@ -16,19 +75,21 @@ type SceneMeta = {
16
75
  duration?: number;
17
76
  /** Opt-in diagram mode; defaults to architecture. */
18
77
  type?: DiagramType;
19
- /** Optional custom progress bar color or gradient. Defaults to rainbow. */
78
+ /** Playback, controls, interaction, and chrome behavior. Source of truth. */
79
+ player?: PlayerConfig;
80
+ /** @deprecated Mirror of `player.chrome.progressColor`. */
20
81
  progressColor?: string;
21
- /** Enable playback and view reset controls toolbar. */
82
+ /** @deprecated Mirror of `player.controls.enabled`. */
22
83
  controls?: boolean;
23
- /** Enable wheel zoom and drag pan. */
84
+ /** @deprecated Mirror of `player.interaction.enabled`. */
24
85
  interactiveViewport?: boolean;
25
- /** Autoplay timeline on load. */
86
+ /** @deprecated Mirror of `player.playback.autoplay`. */
26
87
  autoplay?: boolean;
27
- /** Loop playback when reaching the end. */
88
+ /** @deprecated Mirror of `player.playback.loop`. */
28
89
  loop?: boolean;
29
- /** Show copyright badge. */
90
+ /** @deprecated Mirror of `player.chrome.badge`. */
30
91
  copyright?: boolean;
31
- /** Default playback speed multiplier. */
92
+ /** @deprecated Mirror of `player.playback.rate`. */
32
93
  playbackRate?: number;
33
94
  };
34
95
  type NodeDecl = {
@@ -326,6 +387,36 @@ declare function parse(source: string, opts?: ParseOptions): DiagramAST;
326
387
  declare function compile(ast: DiagramAST): RenderPlan;
327
388
  declare function parseAndCompile(source: string): ParseResult;
328
389
 
390
+ /**
391
+ * Single source of truth for player configuration: schema, aliases, parsing,
392
+ * and default resolution. Hosts (DOM renderer, Astro, MDX, CLI) resolve
393
+ * behavior through `resolvePlayer` instead of re-deriving defaults.
394
+ */
395
+
396
+ /** Groups own their own alias table, so `speed` can mean rate at the player
397
+ * root and the speed buttons inside `controls:`. */
398
+ type PlayerScope = "player" | "playback" | "controls" | "interaction" | "chrome";
399
+ declare const PLAYER_GROUPS: readonly ["playback", "controls", "interaction", "chrome"];
400
+ /** Keys usable as `scene` props and top-level directives. */
401
+ declare const PLAYER_FLAT_KEYS: string[];
402
+ /**
403
+ * Applies one `key value` pair into `config`. Returns an error message when the
404
+ * key is unknown or the value does not fit the setting type.
405
+ */
406
+ declare function applyPlayerSetting(config: PlayerConfig, scope: PlayerScope, key: string, rawValue: string): string | undefined;
407
+ /** Host-supplied overrides always win over script configuration. */
408
+ type PlayerOverrides = {
409
+ autoplay?: boolean;
410
+ loop?: boolean;
411
+ playbackRate?: number;
412
+ copyright?: boolean;
413
+ controls?: boolean;
414
+ interactiveViewport?: boolean;
415
+ progress?: PlayerProgress;
416
+ progressColor?: string;
417
+ };
418
+ declare function resolvePlayer(config?: PlayerConfig, overrides?: PlayerOverrides): ResolvedPlayer;
419
+
329
420
  declare const THEMES: Record<string, ThemeTokens>;
330
421
  declare function resolveTheme(name: string): ThemeTokens;
331
422
 
@@ -537,4 +628,4 @@ declare function resolveOutputPreset(name: string): OutputPreset;
537
628
  /** List all available preset names. */
538
629
  declare function listOutputPresets(): string[];
539
630
 
540
- export { ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitecturePreset, type ArchitectureRule, type ArchitectureViolation, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, CUE_ALIASES, type CardinalPort, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagramAST, type DiagramDiffResult, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type FlowSegment, type GroupBoundary, type GroupDecl, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OutputPreset, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type Point, type PositionedNode, type RenderPlan, type RepairPromptBundle, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VISUAL_PRIMITIVE_TYPES, analyzeAndBuildRepairPrompt, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, decompressMarkdyFromUrlHash, diffDiagramASTs, generateThemeFromBrand, getBoxPortPosition, humanizeId, listOutputPresets, nodeRole, parse, parseAndCompile, resolveArchitectureConfig, resolveOutputPreset, resolveTheme, routeOrthogonalEdge, selectOptimalPorts, validateArchitecture };
631
+ export { ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitecturePreset, type ArchitectureRule, type ArchitectureViolation, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, CUE_ALIASES, type CardinalPort, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagramAST, type DiagramDiffResult, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type FlowSegment, type GroupBoundary, type GroupDecl, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OutputPreset, PLAYER_FLAT_KEYS, PLAYER_GROUPS, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PlayerChromeConfig, type PlayerConfig, type PlayerControlsConfig, type PlayerInteractionConfig, type PlayerOverrides, type PlayerPlaybackConfig, type PlayerProgress, type PlayerScope, type Point, type PositionedNode, type RenderPlan, type RepairPromptBundle, type ResolvedPlayer, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VISUAL_PRIMITIVE_TYPES, analyzeAndBuildRepairPrompt, applyPlayerSetting, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, decompressMarkdyFromUrlHash, diffDiagramASTs, generateThemeFromBrand, getBoxPortPosition, humanizeId, listOutputPresets, nodeRole, parse, parseAndCompile, resolveArchitectureConfig, resolveOutputPreset, resolvePlayer, resolveTheme, routeOrthogonalEdge, selectOptimalPorts, validateArchitecture };
package/dist/index.js CHANGED
@@ -378,6 +378,258 @@ var TECHNICAL_NODE_KINDS = {
378
378
  lock: "distributed"
379
379
  };
380
380
 
381
+ // src/player.ts
382
+ function parseBooleanToken(raw) {
383
+ if (typeof raw === "boolean") return raw;
384
+ const s = String(raw).trim().toLowerCase();
385
+ if (["true", "on", "yes", "1"].includes(s)) return true;
386
+ if (["false", "off", "no", "0"].includes(s)) return false;
387
+ return void 0;
388
+ }
389
+ var CONTROL_KEYS = [
390
+ "play",
391
+ "restart",
392
+ "prevBeat",
393
+ "nextBeat",
394
+ "seek",
395
+ "speed",
396
+ "fit",
397
+ "resetView",
398
+ "svg",
399
+ "share"
400
+ ];
401
+ var INTERACTION_KEYS = ["zoom", "pan", "doubleClickToReset"];
402
+ var PLAYBACK = {
403
+ autoplay: { group: "playback", key: "autoplay", type: "boolean" },
404
+ loop: { group: "playback", key: "loop", type: "boolean" },
405
+ rate: { group: "playback", key: "rate", type: "rate" },
406
+ speed: { group: "playback", key: "rate", type: "rate" },
407
+ playbackRate: { group: "playback", key: "rate", type: "rate" },
408
+ playback_rate: { group: "playback", key: "rate", type: "rate" }
409
+ };
410
+ var CONTROLS = {
411
+ controls: { group: "controls", key: "*", type: "group" },
412
+ play: { group: "controls", key: "play", type: "boolean" },
413
+ playButton: { group: "controls", key: "play", type: "boolean" },
414
+ play_button: { group: "controls", key: "play", type: "boolean" },
415
+ restart: { group: "controls", key: "restart", type: "boolean" },
416
+ restartButton: { group: "controls", key: "restart", type: "boolean" },
417
+ restart_button: { group: "controls", key: "restart", type: "boolean" },
418
+ prevBeat: { group: "controls", key: "prevBeat", type: "boolean" },
419
+ prev_beat: { group: "controls", key: "prevBeat", type: "boolean" },
420
+ nextBeat: { group: "controls", key: "nextBeat", type: "boolean" },
421
+ next_beat: { group: "controls", key: "nextBeat", type: "boolean" },
422
+ speeds: { group: "controls", key: "speeds", type: "rates" },
423
+ speedOptions: { group: "controls", key: "speeds", type: "rates" },
424
+ speed_options: { group: "controls", key: "speeds", type: "rates" },
425
+ seek: { group: "controls", key: "seek", type: "boolean" },
426
+ seekBar: { group: "controls", key: "seek", type: "boolean" },
427
+ seek_bar: { group: "controls", key: "seek", type: "boolean" },
428
+ speed: { group: "controls", key: "speed", type: "boolean" },
429
+ speedControls: { group: "controls", key: "speed", type: "boolean" },
430
+ speed_controls: { group: "controls", key: "speed", type: "boolean" },
431
+ fit: { group: "controls", key: "fit", type: "boolean" },
432
+ fitView: { group: "controls", key: "fit", type: "boolean" },
433
+ fit_view: { group: "controls", key: "fit", type: "boolean" },
434
+ fitViewButton: { group: "controls", key: "fit", type: "boolean" },
435
+ fit_view_button: { group: "controls", key: "fit", type: "boolean" },
436
+ resetView: { group: "controls", key: "resetView", type: "boolean" },
437
+ reset_view: { group: "controls", key: "resetView", type: "boolean" },
438
+ resetViewButton: { group: "controls", key: "resetView", type: "boolean" },
439
+ reset_view_button: { group: "controls", key: "resetView", type: "boolean" },
440
+ svg: { group: "controls", key: "svg", type: "boolean" },
441
+ exportSvg: { group: "controls", key: "svg", type: "boolean" },
442
+ export_svg: { group: "controls", key: "svg", type: "boolean" },
443
+ share: { group: "controls", key: "share", type: "boolean" },
444
+ shareLink: { group: "controls", key: "share", type: "boolean" },
445
+ share_link: { group: "controls", key: "share", type: "boolean" }
446
+ };
447
+ var INTERACTION = {
448
+ interactive: { group: "interaction", key: "*", type: "group" },
449
+ interactiveViewport: { group: "interaction", key: "*", type: "group" },
450
+ interactive_viewport: { group: "interaction", key: "*", type: "group" },
451
+ zoom: { group: "interaction", key: "zoom", type: "boolean" },
452
+ allowZoom: { group: "interaction", key: "zoom", type: "boolean" },
453
+ allow_zoom: { group: "interaction", key: "zoom", type: "boolean" },
454
+ pan: { group: "interaction", key: "pan", type: "boolean" },
455
+ allowPan: { group: "interaction", key: "pan", type: "boolean" },
456
+ allow_pan: { group: "interaction", key: "pan", type: "boolean" },
457
+ clickToPlay: { group: "interaction", key: "clickToPlay", type: "boolean" },
458
+ click_to_play: { group: "interaction", key: "clickToPlay", type: "boolean" },
459
+ keyboard: { group: "interaction", key: "keyboard", type: "boolean" },
460
+ shortcuts: { group: "interaction", key: "keyboard", type: "boolean" },
461
+ doubleClickToReset: { group: "interaction", key: "doubleClickToReset", type: "boolean" },
462
+ double_click_to_reset: { group: "interaction", key: "doubleClickToReset", type: "boolean" }
463
+ };
464
+ var CHROME = {
465
+ badge: { group: "chrome", key: "badge", type: "boolean" },
466
+ copyright: { group: "chrome", key: "badge", type: "boolean" },
467
+ progress: { group: "chrome", key: "progress", type: "progress" },
468
+ progressBar: { group: "chrome", key: "progress", type: "progress" },
469
+ progress_bar: { group: "chrome", key: "progress", type: "progress" },
470
+ sceneBoundaryProgress: { group: "chrome", key: "progress", type: "progress" },
471
+ progressColor: { group: "chrome", key: "progressColor", type: "color" },
472
+ progress_color: { group: "chrome", key: "progressColor", type: "color" },
473
+ progressBarColor: { group: "chrome", key: "progressColor", type: "color" },
474
+ progress_bar_color: { group: "chrome", key: "progressColor", type: "color" }
475
+ };
476
+ var FLAT = {
477
+ ...PLAYBACK,
478
+ ...CHROME,
479
+ controls: CONTROLS.controls,
480
+ playButton: CONTROLS.playButton,
481
+ play_button: CONTROLS.play_button,
482
+ restartButton: CONTROLS.restartButton,
483
+ restart_button: CONTROLS.restart_button,
484
+ seek: CONTROLS.seek,
485
+ seekBar: CONTROLS.seekBar,
486
+ seek_bar: CONTROLS.seek_bar,
487
+ speedControls: CONTROLS.speedControls,
488
+ speed_controls: CONTROLS.speed_controls,
489
+ speeds: CONTROLS.speeds,
490
+ speedOptions: CONTROLS.speedOptions,
491
+ speed_options: CONTROLS.speed_options,
492
+ prevBeat: CONTROLS.prevBeat,
493
+ prev_beat: CONTROLS.prev_beat,
494
+ nextBeat: CONTROLS.nextBeat,
495
+ next_beat: CONTROLS.next_beat,
496
+ keyboard: INTERACTION.keyboard,
497
+ shortcuts: INTERACTION.shortcuts,
498
+ fitView: CONTROLS.fitView,
499
+ fit_view: CONTROLS.fit_view,
500
+ fitViewButton: CONTROLS.fitViewButton,
501
+ fit_view_button: CONTROLS.fit_view_button,
502
+ resetViewButton: CONTROLS.resetViewButton,
503
+ reset_view_button: CONTROLS.reset_view_button,
504
+ exportSvg: CONTROLS.exportSvg,
505
+ export_svg: CONTROLS.export_svg,
506
+ shareLink: CONTROLS.shareLink,
507
+ share_link: CONTROLS.share_link,
508
+ interactive: INTERACTION.interactive,
509
+ interactiveViewport: INTERACTION.interactiveViewport,
510
+ interactive_viewport: INTERACTION.interactive_viewport,
511
+ allowZoom: INTERACTION.allowZoom,
512
+ allow_zoom: INTERACTION.allow_zoom,
513
+ allowPan: INTERACTION.allowPan,
514
+ allow_pan: INTERACTION.allow_pan,
515
+ clickToPlay: INTERACTION.clickToPlay,
516
+ click_to_play: INTERACTION.click_to_play,
517
+ doubleClickToReset: INTERACTION.doubleClickToReset,
518
+ double_click_to_reset: INTERACTION.double_click_to_reset
519
+ };
520
+ var SCOPES = {
521
+ player: FLAT,
522
+ playback: PLAYBACK,
523
+ controls: CONTROLS,
524
+ interaction: INTERACTION,
525
+ // `color` is unambiguous inside the block, but too generic at the root.
526
+ chrome: { ...CHROME, color: CHROME.progressColor }
527
+ };
528
+ var PLAYER_GROUPS = ["playback", "controls", "interaction", "chrome"];
529
+ var PLAYER_FLAT_KEYS = Object.keys(FLAT);
530
+ function applyPlayerSetting(config, scope, key, rawValue) {
531
+ const setting = SCOPES[scope][key];
532
+ if (!setting) return `unknown player property '${key}'`;
533
+ const value = unquote(rawValue).trim();
534
+ const group = config[setting.group] ??= {};
535
+ if (setting.type === "boolean" || setting.type === "group") {
536
+ const parsed = value ? parseBooleanToken(value) : true;
537
+ if (parsed === void 0) return `player property '${key}' expects true or false`;
538
+ if (setting.type === "boolean") group[setting.key] = parsed;
539
+ else for (const child of setting.group === "controls" ? CONTROL_KEYS : INTERACTION_KEYS) group[child] = parsed;
540
+ return void 0;
541
+ }
542
+ if (setting.type === "rate") {
543
+ const rate = Number(value);
544
+ if (!Number.isFinite(rate) || rate <= 0) return `player property '${key}' expects a positive number`;
545
+ group[setting.key] = rate;
546
+ return void 0;
547
+ }
548
+ if (setting.type === "rates") {
549
+ const rates = value.split(/[\s,]+/).filter(Boolean).map(Number);
550
+ if (!rates.length || rates.some((rate) => !Number.isFinite(rate) || rate <= 0)) {
551
+ return `player property '${key}' expects a list of positive numbers`;
552
+ }
553
+ group[setting.key] = rates;
554
+ return void 0;
555
+ }
556
+ if (setting.type === "color") {
557
+ if (value) group[setting.key] = value;
558
+ return void 0;
559
+ }
560
+ const mode = toProgressMode(value);
561
+ if (mode) group.progress = mode;
562
+ else if (value) group.progressColor = value;
563
+ return void 0;
564
+ }
565
+ function toProgressMode(value) {
566
+ const lower = value.toLowerCase();
567
+ if (lower === "none" || lower === "bar" || lower === "boundary") return lower;
568
+ const bool = parseBooleanToken(lower);
569
+ if (bool === true) return "boundary";
570
+ if (bool === false) return "none";
571
+ return void 0;
572
+ }
573
+ function unquote(raw) {
574
+ const value = raw.trim();
575
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
576
+ return value.slice(1, -1);
577
+ }
578
+ return value;
579
+ }
580
+ function resolvePlayer(config = {}, overrides = {}) {
581
+ const playback = config.playback ?? {};
582
+ const interaction = config.interaction ?? {};
583
+ const chrome = config.chrome ?? {};
584
+ const controlsOn = overrides.controls !== false && (overrides.controls === true || config.controls !== void 0);
585
+ const controls = {
586
+ play: controlsOn && (config.controls?.play ?? true),
587
+ restart: controlsOn && (config.controls?.restart ?? true),
588
+ prevBeat: controlsOn && (config.controls?.prevBeat ?? true),
589
+ nextBeat: controlsOn && (config.controls?.nextBeat ?? true),
590
+ seek: controlsOn && (config.controls?.seek ?? true),
591
+ speed: controlsOn && (config.controls?.speed ?? true),
592
+ fit: controlsOn && (config.controls?.fit ?? true),
593
+ resetView: controlsOn && (config.controls?.resetView ?? true),
594
+ svg: controlsOn && (config.controls?.svg ?? true),
595
+ share: controlsOn && (config.controls?.share ?? true)
596
+ };
597
+ const interactionOn = overrides.interactiveViewport !== false && (overrides.interactiveViewport === true || config.interaction !== void 0 || overrides.controls === true);
598
+ const gestures = {
599
+ zoom: interactionOn && (interaction.zoom ?? true),
600
+ pan: interactionOn && (interaction.pan ?? true),
601
+ doubleClickToReset: interactionOn && (interaction.doubleClickToReset ?? true)
602
+ };
603
+ const interactionEnabled = gestures.zoom || gestures.pan || gestures.doubleClickToReset;
604
+ const resetView = controls.resetView && interactionEnabled;
605
+ const controlsEnabled = controls.play || controls.restart || controls.prevBeat || controls.nextBeat || controls.seek || controls.speed || controls.fit || controls.svg || controls.share || resetView;
606
+ const rate = overrides.playbackRate ?? playback.rate ?? 1;
607
+ return {
608
+ playback: {
609
+ autoplay: overrides.autoplay ?? playback.autoplay ?? true,
610
+ loop: overrides.loop ?? playback.loop ?? true,
611
+ rate: Number.isFinite(rate) && rate > 0 ? rate : 1
612
+ },
613
+ controls: {
614
+ ...controls,
615
+ resetView,
616
+ enabled: controlsEnabled,
617
+ speeds: config.controls?.speeds?.length ? config.controls.speeds : [0.5, 1, 2]
618
+ },
619
+ interaction: {
620
+ ...gestures,
621
+ enabled: interactionEnabled,
622
+ clickToPlay: interaction.clickToPlay ?? true,
623
+ keyboard: interaction.keyboard ?? false
624
+ },
625
+ chrome: {
626
+ badge: overrides.copyright ?? chrome.badge ?? true,
627
+ progress: overrides.progress ?? chrome.progress ?? "boundary",
628
+ progressColor: overrides.progressColor ?? chrome.progressColor
629
+ }
630
+ };
631
+ }
632
+
381
633
  // src/registry.ts
382
634
  var NODE_KINDS = /* @__PURE__ */ new Set([
383
635
  ...TECHNICAL_NODE_TYPES,
@@ -433,23 +685,7 @@ var SCENE_KEYS = /* @__PURE__ */ new Set([
433
685
  "direction",
434
686
  "layout",
435
687
  "type",
436
- "progressColor",
437
- "progressBarColor",
438
- "progress_color",
439
- "progress_bar_color",
440
- "progress",
441
- "progressBar",
442
- "sceneBoundaryProgress",
443
- "controls",
444
- "interactive",
445
- "interactiveViewport",
446
- "interactive_viewport",
447
- "autoplay",
448
- "loop",
449
- "copyright",
450
- "playbackRate",
451
- "playback_rate",
452
- "speed"
688
+ ...PLAYER_FLAT_KEYS
453
689
  ]);
454
690
  function nodeRole(kind) {
455
691
  const canonical = canonicalNodeKind(kind);
@@ -1658,7 +1894,7 @@ function compilePlan(ast, theme) {
1658
1894
  ...beats.map((b) => b.end),
1659
1895
  1
1660
1896
  );
1661
- const title = ast.meta.title ?? "Architecture Diagram";
1897
+ const title = ast.meta.title ?? "";
1662
1898
  return {
1663
1899
  meta: ast.meta,
1664
1900
  theme,
@@ -1725,9 +1961,9 @@ var THEMES = {
1725
1961
  canvas: "#f6f8fc",
1726
1962
  surface: "#ffffff",
1727
1963
  surfaceRaised: "#f0f4f9",
1728
- border: "#d3dde9",
1964
+ border: "#cbd5e1",
1729
1965
  text: "#0f1c2e",
1730
- textMuted: "#5a6b80",
1966
+ textMuted: "#475569",
1731
1967
  gridMinor: "rgba(15, 23, 42, 0.06)",
1732
1968
  gridMajor: "rgba(15, 23, 42, 0.10)",
1733
1969
  vignette: "rgba(226, 232, 240, 0.6)",
@@ -1788,7 +2024,7 @@ var THEMES = {
1788
2024
  surfaceRaised: "#f8fafc",
1789
2025
  border: "#e2e8f0",
1790
2026
  text: "#0f172a",
1791
- textMuted: "#64748b",
2027
+ textMuted: "#475569",
1792
2028
  paper: "#fafafa",
1793
2029
  ink: "#0f172a",
1794
2030
  muted: "#6b7280",
@@ -2357,14 +2593,30 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
2357
2593
  }
2358
2594
  return { body, nextIdx: i };
2359
2595
  }
2360
- function parseBooleanToken(raw) {
2361
- if (typeof raw === "boolean") return raw;
2362
- const s = String(raw).trim().toLowerCase();
2363
- if (["true", "on", "yes", "1"].includes(s)) return true;
2364
- if (["false", "off", "no", "0"].includes(s)) return false;
2365
- return void 0;
2596
+ var PLAYER_KEYWORDS = [...PLAYER_FLAT_KEYS].sort((a, b) => b.length - a.length).join("|");
2597
+ var TOP_LEVEL_KEYWORDS_RE = new RegExp(
2598
+ `^(scene|player|layout|pattern|group|annotation|edge|beat|var|style|${PLAYER_KEYWORDS})\\b`
2599
+ );
2600
+ var PLAYER_DIRECTIVE_RE = new RegExp(`^(${PLAYER_KEYWORDS})\\b(?:\\s*[:=]?\\s*(.+))?$`);
2601
+ var PLAYER_GROUP_RE = /^(playback|controls|interaction|chrome)\s*:\s*$/;
2602
+ var PLAYER_SETTING_RE = /^(\w+)(?:\s*[:=]\s*|\s+)?(.*)$/;
2603
+ var PLAYER_FLAT_KEY_SET = new Set(PLAYER_FLAT_KEYS);
2604
+ function mirrorLegacySceneMeta(meta) {
2605
+ const player = meta.player;
2606
+ if (!player) return meta;
2607
+ const { playback, controls, interaction, chrome } = player;
2608
+ if (playback?.autoplay !== void 0) meta.autoplay = playback.autoplay;
2609
+ if (playback?.loop !== void 0) meta.loop = playback.loop;
2610
+ if (playback?.rate !== void 0) meta.playbackRate = playback.rate;
2611
+ if (controls) meta.controls = Object.values(controls).some(Boolean);
2612
+ if (interaction) {
2613
+ meta.interactiveViewport = Boolean(interaction.zoom ?? true) || Boolean(interaction.pan ?? true);
2614
+ }
2615
+ if (chrome?.badge !== void 0) meta.copyright = chrome.badge;
2616
+ if (chrome?.progress === "none") meta.progressColor = "none";
2617
+ else if (chrome?.progressColor !== void 0) meta.progressColor = chrome.progressColor;
2618
+ return meta;
2366
2619
  }
2367
- var TOP_LEVEL_KEYWORDS_RE = /^(scene|layout|pattern|group|annotation|edge|beat|var|style|controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b/;
2368
2620
  function isTopLevelStatement(line) {
2369
2621
  if (line === "}") return true;
2370
2622
  if (TOP_LEVEL_KEYWORDS_RE.test(line)) return true;
@@ -2617,6 +2869,35 @@ function parse(source, opts = {}) {
2617
2869
  const lineNo = block.line;
2618
2870
  const unsupportedMessage = unsupportedSyntaxMessage(line);
2619
2871
  if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
2872
+ if (/^player\s*:\s*$/.test(line)) {
2873
+ const { body, nextIdx } = readIndentedBody(blocks, i + 1, block.indent);
2874
+ if (body.length === 0) throw new ParseError("player block requires at least one setting", lineNo);
2875
+ const player = meta.player ??= {};
2876
+ const applySetting = (scope, setting) => {
2877
+ const match = setting.text.match(PLAYER_SETTING_RE);
2878
+ const error = applyPlayerSetting(player, scope, match?.[1] ?? "", match?.[2] ?? "");
2879
+ if (error) diagnostics.push({ severity: "warning", message: error, line: setting.line });
2880
+ };
2881
+ let settingIdx = 0;
2882
+ while (settingIdx < body.length) {
2883
+ const setting = body[settingIdx];
2884
+ const groupMatch = setting.text.match(PLAYER_GROUP_RE);
2885
+ if (groupMatch) {
2886
+ const scope = groupMatch[1];
2887
+ const { body: childBody, nextIdx: childNextIdx } = readIndentedBody(body, settingIdx + 1, setting.indent);
2888
+ if (childBody.length === 0) {
2889
+ throw new ParseError(`player ${scope} block requires at least one setting`, setting.line);
2890
+ }
2891
+ for (const child of childBody) applySetting(scope, child);
2892
+ settingIdx = childNextIdx;
2893
+ continue;
2894
+ }
2895
+ applySetting("player", setting);
2896
+ settingIdx++;
2897
+ }
2898
+ i = nextIdx;
2899
+ continue;
2900
+ }
2620
2901
  if (line.startsWith("scene")) {
2621
2902
  const rest2 = line.slice(5).trim();
2622
2903
  if (line.endsWith("{")) {
@@ -2643,28 +2924,9 @@ function parse(source, opts = {}) {
2643
2924
  else if (k === "duration") meta.duration = Number(v);
2644
2925
  else if (k === "theme") meta.theme = String(v);
2645
2926
  else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
2646
- else if (k === "progressColor" || k === "progressBarColor" || k === "progress_color" || k === "progress_bar_color" || k === "progress") {
2647
- meta.progressColor = String(v);
2648
- } else if (k === "progressBar" || k === "sceneBoundaryProgress") {
2649
- const bool = parseBooleanToken(v);
2650
- if (bool !== void 0) {
2651
- if (!bool) meta.progressColor = "none";
2652
- } else {
2653
- meta.progressColor = String(v);
2654
- }
2655
- } else if (k === "controls") {
2656
- meta.controls = parseBooleanToken(v) ?? true;
2657
- } else if (k === "interactive" || k === "interactiveViewport" || k === "interactive_viewport") {
2658
- meta.interactiveViewport = parseBooleanToken(v) ?? true;
2659
- } else if (k === "autoplay") {
2660
- meta.autoplay = parseBooleanToken(v) ?? true;
2661
- } else if (k === "loop") {
2662
- meta.loop = parseBooleanToken(v) ?? true;
2663
- } else if (k === "copyright") {
2664
- meta.copyright = parseBooleanToken(v) ?? true;
2665
- } else if (k === "playbackRate" || k === "playback_rate" || k === "speed") {
2666
- const r = Number(v);
2667
- if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2927
+ else if (PLAYER_FLAT_KEY_SET.has(k)) {
2928
+ const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
2929
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
2668
2930
  } else if (k === "type") {
2669
2931
  const t = String(v).toLowerCase();
2670
2932
  if (!DIAGRAM_TYPES.has(t)) {
@@ -2677,33 +2939,10 @@ function parse(source, opts = {}) {
2677
2939
  i++;
2678
2940
  continue;
2679
2941
  }
2680
- const directiveMatch = line.match(
2681
- /^(controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b(?:\s*[:=]?\s*(.+))?$/
2682
- );
2942
+ const directiveMatch = line.match(PLAYER_DIRECTIVE_RE);
2683
2943
  if (directiveMatch) {
2684
- const keyword = directiveMatch[1];
2685
- const rawVal = (directiveMatch[2] ?? "").trim();
2686
- const val = rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'") ? rawVal.slice(1, -1) : rawVal;
2687
- if (keyword === "controls") {
2688
- meta.controls = val ? parseBooleanToken(val) ?? true : true;
2689
- } else if (keyword === "interactive" || keyword === "interactiveViewport" || keyword === "interactive_viewport") {
2690
- meta.interactiveViewport = val ? parseBooleanToken(val) ?? true : true;
2691
- } else if (keyword === "autoplay") {
2692
- meta.autoplay = val ? parseBooleanToken(val) ?? true : true;
2693
- } else if (keyword === "loop") {
2694
- meta.loop = val ? parseBooleanToken(val) ?? true : true;
2695
- } else if (keyword === "copyright") {
2696
- meta.copyright = val ? parseBooleanToken(val) ?? true : true;
2697
- } else if (keyword === "playbackRate" || keyword === "playback_rate" || keyword === "speed") {
2698
- const r = Number(val);
2699
- if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2700
- } else if (keyword === "progressColor" || keyword === "progressBarColor" || keyword === "progress_color" || keyword === "progress_bar_color" || keyword === "progress") {
2701
- if (val) meta.progressColor = val;
2702
- } else if (keyword === "progressBar") {
2703
- const bool = parseBooleanToken(val);
2704
- if (bool === false) meta.progressColor = "none";
2705
- else if (val) meta.progressColor = val;
2706
- }
2944
+ const error = applyPlayerSetting(meta.player ??= {}, "player", directiveMatch[1], directiveMatch[2] ?? "");
2945
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
2707
2946
  i++;
2708
2947
  continue;
2709
2948
  }
@@ -2859,7 +3098,7 @@ function parse(source, opts = {}) {
2859
3098
  throw new ParseError(`unexpected statement`, lineNo);
2860
3099
  }
2861
3100
  const ast = {
2862
- meta: { ...meta, title: title || void 0 },
3101
+ meta: { ...mirrorLegacySceneMeta(meta), title: title || void 0 },
2863
3102
  styles,
2864
3103
  nodes,
2865
3104
  edges,
@@ -3579,7 +3818,7 @@ function diffDiagramASTs(beforeAST, afterAST) {
3579
3818
  summaryLines.push("");
3580
3819
  }
3581
3820
  const evolutionLines = [
3582
- `scene "Architecture Evolution" theme=${afterAST.meta.theme || "paper"}`,
3821
+ `scene theme=${afterAST.meta.theme || "paper"}`,
3583
3822
  `layout ${afterAST.meta.direction || "LR"}`,
3584
3823
  ""
3585
3824
  ];
@@ -3947,6 +4186,8 @@ export {
3947
4186
  NODE_ALIASES,
3948
4187
  NODE_KINDS,
3949
4188
  OUTPUT_PRESETS,
4189
+ PLAYER_FLAT_KEYS,
4190
+ PLAYER_GROUPS,
3950
4191
  ParseError,
3951
4192
  SCENE_KEYS,
3952
4193
  TECHNICAL_NODE_KINDS,
@@ -3954,6 +4195,7 @@ export {
3954
4195
  THEMES,
3955
4196
  VISUAL_PRIMITIVE_TYPES,
3956
4197
  analyzeAndBuildRepairPrompt,
4198
+ applyPlayerSetting,
3957
4199
  canonicalNodeKind,
3958
4200
  classifyTechnology,
3959
4201
  compile,
@@ -3970,6 +4212,7 @@ export {
3970
4212
  parseAndCompile,
3971
4213
  resolveArchitectureConfig,
3972
4214
  resolveOutputPreset,
4215
+ resolvePlayer,
3973
4216
  resolveTheme,
3974
4217
  routeOrthogonalEdge,
3975
4218
  selectOptimalPorts,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Diagram-native MarkdyScript parser and compiler (auto-layout, edge routing, cue scheduling) — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",