@markdstage/markdstage 3.2.0 → 3.4.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.
@@ -91,6 +91,60 @@ export function serializeThemeVariables(variables) {
91
91
  .join("");
92
92
  }
93
93
 
94
+ // Mermaid's "base" theme accepts a `themeVariables` palette instead of one of
95
+ // its built-in named themes (dark/default/neutral/forest). Deriving that
96
+ // palette from the rendered deck's custom properties makes Mermaid diagrams
97
+ // share the slide's background, border, and text colors instead of only
98
+ // approximating the deck theme, and it reuses the same primary/secondary
99
+ // roles as the Architecture DSL (nodes: surface+border+fg, groups:
100
+ // accent-soft+accent-line+accent-strong) so both diagram types match.
101
+ //
102
+ // `secondaryColor`/`tertiaryColor` also back many categorical fills across
103
+ // Mermaid's diagram types (pie slices, git graph nodes, venn/quadrant charts,
104
+ // activation highlights, ...), so they must stay solid and clearly visible
105
+ // against `--bg` rather than reusing the translucent `--accent-soft` wash or
106
+ // `--bg` itself, which rendered those fills nearly invisible. `noteBkgColor`/
107
+ // `noteBorderColor`/`noteTextColor` are hardcoded by Mermaid's base theme
108
+ // (always a pale yellow) unless set explicitly, so sequence-diagram notes
109
+ // need their own override to follow the deck theme too.
110
+ export function mermaidThemeVariables(style) {
111
+ const read = (name) => style.getPropertyValue(name).trim();
112
+ const background = read("--bg");
113
+ const surface = read("--surface");
114
+ const border = read("--border");
115
+ const foreground = read("--fg");
116
+ const muted = read("--muted");
117
+ const accent = read("--accent");
118
+ const accentStrong = read("--accent-strong");
119
+ const accentSoft = read("--accent-soft");
120
+ const accentLine = read("--accent-line");
121
+
122
+ return {
123
+ background,
124
+ primaryColor: surface,
125
+ primaryTextColor: foreground,
126
+ primaryBorderColor: border,
127
+ secondaryColor: accentStrong,
128
+ secondaryTextColor: background,
129
+ secondaryBorderColor: accentLine,
130
+ tertiaryColor: muted,
131
+ tertiaryTextColor: background,
132
+ tertiaryBorderColor: border,
133
+ lineColor: accent,
134
+ textColor: foreground,
135
+ mainBkg: surface,
136
+ nodeBorder: border,
137
+ clusterBkg: accentSoft,
138
+ clusterBorder: accentLine,
139
+ titleColor: foreground,
140
+ edgeLabelBackground: background,
141
+ noteBkgColor: accentSoft,
142
+ noteBorderColor: accentLine,
143
+ noteTextColor: accentStrong,
144
+ pie1: accent,
145
+ };
146
+ }
147
+
94
148
  function assertPlainObject(value, path) {
95
149
  if (!value || typeof value !== "object" || Array.isArray(value)) {
96
150
  throw new Error(`${path} must be an object`);
@@ -581,6 +581,9 @@ export async function startArchitectureEditorServer({
581
581
  if (
582
582
  [
583
583
  "/renderer/architecture.mjs",
584
+ "/renderer/architecture-scene.mjs",
585
+ "/renderer/scene-graph.mjs",
586
+ "/renderer/scene-svg.mjs",
584
587
  "/renderer/architecture-contract.mjs",
585
588
  "/renderer/architecture-diagnostics.mjs",
586
589
  "/renderer/architecture-edit.mjs",
@@ -112,14 +112,25 @@ export async function createDeckSession({
112
112
  // An explicit --workspace wins; otherwise confine the deck to its Git
113
113
  // repository root (or the folder holding the Markdown file).
114
114
  const deckDirectory = file ? resolve(file, "..") : process.cwd();
115
- const root = workspaceRoot
115
+ const requestedRoot = workspaceRoot
116
116
  ? resolve(workspaceRoot)
117
117
  : resolveWorkspaceRoot(deckDirectory, deckDirectory);
118
- const resolved = await resolveDeckFile(file, root);
118
+ let root;
119
+ try {
120
+ root = await realpath(requestedRoot);
121
+ } catch (_) {
122
+ throw new MarkdStageError(
123
+ "workspace_not_found",
124
+ `Could not read workspace directory: ${requestedRoot}`,
125
+ );
126
+ }
127
+ const resolved = file ? await resolveDeckFile(file, root) : null;
119
128
  const session = {
120
- file: resolved.path,
121
- workspaceRoot: resolved.workspaceRoot,
122
- sourceName: workspaceRelative(resolved.workspaceRoot, resolved.path),
129
+ file: resolved?.path ?? "",
130
+ workspaceRoot: resolved?.workspaceRoot ?? root,
131
+ sourceName: resolved
132
+ ? workspaceRelative(resolved.workspaceRoot, resolved.path)
133
+ : "",
123
134
  url: "",
124
135
  version: 0,
125
136
  deckVersion: 0,
@@ -145,6 +156,9 @@ export async function createDeckSession({
145
156
  };
146
157
 
147
158
  session.load = async ({ preserveIndex = false } = {}) => {
159
+ if (!session.file) {
160
+ throw new MarkdStageError("no_deck", "Open a Markdown file first.");
161
+ }
148
162
  const { markdown, slides } = await readDeckSlides(session.file);
149
163
  const selection = resolveDeckTheme({
150
164
  slides,
@@ -176,6 +190,23 @@ export async function createDeckSession({
176
190
  return session.slides.length;
177
191
  };
178
192
 
193
+ session.openFile = async (nextFile, { preserveIndex = false } = {}) => {
194
+ const next = await resolveDeckFile(nextFile, session.workspaceRoot);
195
+ const previous = {
196
+ file: session.file,
197
+ sourceName: session.sourceName,
198
+ };
199
+ session.file = next.path;
200
+ session.sourceName = workspaceRelative(next.workspaceRoot, next.path);
201
+ try {
202
+ return await session.load({ preserveIndex });
203
+ } catch (error) {
204
+ session.file = previous.file;
205
+ session.sourceName = previous.sourceName;
206
+ throw error;
207
+ }
208
+ };
209
+
179
210
  session.navigate = (target) => {
180
211
  const next = clampIndex(target, session.slides.length);
181
212
  if (next === session.index) return false;
@@ -185,6 +216,6 @@ export async function createDeckSession({
185
216
  return true;
186
217
  };
187
218
 
188
- await session.load();
219
+ if (session.file) await session.load();
189
220
  return session;
190
221
  }
@@ -707,7 +707,7 @@ function tableXml(element, path, id, relationships) {
707
707
  return `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${id}" name="Table ${id}"/><p:cNvGraphicFramePr/><p:nvPr/></p:nvGraphicFramePr>${xfrmXml(bounds, "p:xfrm")}<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table"><a:tbl><a:tblPr firstRow="1" bandRow="1"/><a:tblGrid>${columns}</a:tblGrid>${rows}</a:tbl></a:graphicData></a:graphic></p:graphicFrame>`;
708
708
  }
709
709
 
710
- function arrowXml(value, path) {
710
+ function arrowXml(value, path, end = "tailEnd") {
711
711
  if (value === undefined || value === null || value === false || value === "none") {
712
712
  return "";
713
713
  }
@@ -722,7 +722,7 @@ function arrowXml(value, path) {
722
722
  oval: "oval",
723
723
  }[value];
724
724
  if (!type) fail(`${path} is not a supported arrow end`);
725
- return `<a:tailEnd type="${type}"/>`;
725
+ return `<a:${end} type="${type}"/>`;
726
726
  }
727
727
 
728
728
  function connectorXml(element, path, nextId, relationships) {
@@ -756,6 +756,7 @@ function connectorXml(element, path, nextId, relationships) {
756
756
  const y = Math.min(start.y, end.y);
757
757
  const flipH = end.x < start.x ? ' flipH="1"' : "";
758
758
  const flipV = end.y < start.y ? ' flipV="1"' : "";
759
+ const head = index === 0 ? arrowXml(element.arrowStart, `${path}.arrowStart`, "headEnd") : "";
759
760
  const tail =
760
761
  index === points.length - 2
761
762
  ? arrowXml(element.arrowEnd, `${path}.arrowEnd`)
@@ -763,7 +764,7 @@ function connectorXml(element, path, nextId, relationships) {
763
764
  shapes.push(
764
765
  `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="Connector ${id}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${emu(x)}" y="${emu(y)}"/><a:ext cx="${emu(Math.abs(end.x - start.x))}" cy="${emu(Math.abs(end.y - start.y))}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:ln w="${emu(width)}"><a:solidFill><a:srgbClr val="${color.hex}">${
765
766
  alpha < 100000 ? `<a:alpha val="${alpha}"/>` : ""
766
- }</a:srgbClr></a:solidFill>${dash}${tail}</a:ln></p:spPr></p:sp>`,
767
+ }</a:srgbClr></a:solidFill>${dash}${head}${tail}</a:ln></p:spPr></p:sp>`,
767
768
  );
768
769
  }
769
770
  if (element.label !== undefined) {