@markdstage/markdstage 3.3.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.
package/README.md CHANGED
@@ -16,8 +16,8 @@ exported PDF, or hybrid editable PowerPoint deck.
16
16
  ## Install
17
17
 
18
18
  ```console
19
- npx @markdstage/markdstage present slides.md
20
- npx @markdstage/markdstage preview slides.md
19
+ npx @markdstage/markdstage
20
+ npx @markdstage/markdstage slides.md
21
21
  npm install --global @markdstage/markdstage
22
22
  ```
23
23
 
@@ -31,6 +31,8 @@ npm install --global .\markdstage-markdstage-<version>.tgz
31
31
  ## Commands
32
32
 
33
33
  ```console
34
+ markdstage
35
+ markdstage slides.md
34
36
  markdstage present slides.md
35
37
  markdstage preview slides.md --watch
36
38
  markdstage validate slides.md --json
@@ -43,10 +45,14 @@ markdstage skill install --target codex
43
45
  markdstage skill install --target claude
44
46
  ```
45
47
 
48
+ `markdstage` opens an empty Canvas-equivalent UI for the current workspace.
49
+ Choose **Open Markdown** to load a deck. `markdstage slides.md` opens the same UI
50
+ in slide view with automatic refresh enabled.
51
+
46
52
  | Command | Description |
47
53
  | --- | --- |
48
- | `present` | Opens the presenter view with the current slide, next-slide preview, speaker notes, and navigation. Select **Start presentation** to open the synchronized audience-facing window; select **End presentation** to close it. `--watch` reloads on save, and `--no-open` serves the presenter URL without launching a browser. |
49
- | `preview` | Serves the deck on loopback and opens the MarkdStage preview: navigation, presenter view, next-slide preview, speaker notes, overview, custom themes, Mermaid, Architecture DSL, and local assets. `--watch` reloads on save while preserving the current slide, keeps the last valid deck when a save is broken, and enables Architecture editing. Without `--watch`, the source is read-only. `--no-open` serves the deck only. |
54
+ | `present` | Opens the full MarkdStage UI in presenter view. Open Markdown, automatic refresh, editing, export, and audience controls remain available. `--watch` starts in live mode, and `--no-open` serves the UI without launching a browser. |
55
+ | `preview` | Opens the same full UI in slide view. It is a compatibility/convenience entry point; `--watch` starts in live mode, and `--no-open` serves the UI without launching a browser. |
50
56
  | `validate` | Checks deck structure, Architecture DSL blocks, themes, and theme paths. |
51
57
  | `inspect` | Reports the same compact 1280x720 clipping diagnostics as the canvas `inspect_layout` action. `--slide <n>` limits it to one page, `--all` includes slides that fit, `--fail-on-issues` exits with code 5. |
52
58
  | `capture` | Writes 1280x720 PNG files. Without `--pages` only the slides reported as clipped are captured. |
@@ -55,19 +61,24 @@ markdstage skill install --target claude
55
61
  | `skill` | Installs or checks the portable Agent Skills for Codex (`.agents/skills/markdstage/`), Claude Code (`.claude/skills/markdstage/`), and GitHub Copilot (`.github/skills/markdstage/`). Locally modified files are never overwritten without `--force`. |
56
62
  | `help` | Shows the overview, or the help for one command. `markdstage help <command>` prints the same text as `markdstage <command> --help`. |
57
63
 
58
- Global options: `--workspace <dir>`, `--theme <name>`, `--theme-file <path>`,
59
- `--json`, `--help`, `--version`.
64
+ Application options: `--workspace <dir>`, `--theme <name>`,
65
+ `--theme-file <path>`, `--no-open`, and `--json`. Use `--help` and `--version`
66
+ for global information.
60
67
 
61
68
  ## Architecture editing
62
69
 
63
- Run `markdstage preview slides.md --watch` for the live authoring workflow. The
64
- browser starts in viewing mode. Select the pencil control to move Architecture
65
- elements; those placement changes are saved atomically to the matching
66
- `architecture` fence. Select **Advanced edit** to add, update, duplicate,
67
- reparent, or delete elements in the detailed designer, then select **Save**.
70
+ Run `markdstage slides.md` for the live authoring workflow. The browser starts
71
+ in the fixed 16:9 output preview and remains in viewing mode.
72
+ Select **Output preview** to switch to the retained responsive layout. Select
73
+ the pencil control to move Architecture elements; editing automatically switches
74
+ to the responsive layout, and those placement changes are saved atomically to
75
+ the matching `architecture` fence. Select **Advanced edit** to add, update,
76
+ duplicate, reparent, or delete elements in the detailed designer, then select
77
+ **Save**.
68
78
 
69
79
  The server rejects a save if the Markdown changed outside the editor. Successful
70
- saves reload the watched deck without changing the current slide. Presenter,
80
+ saves reload the watched deck without changing the current slide. Automatic
81
+ refresh can be toggled without disabling Architecture editing. Presenter,
71
82
  capture, inspect, and export views contain no editing UI.
72
83
 
73
84
  ```console
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdstage/markdstage",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Present, validate, inspect, capture, and export MarkdStage Markdown decks from the command line — no Copilot canvas required.",
5
5
  "license": "MIT",
6
6
  "author": "runceel",
package/shared/README.md CHANGED
@@ -118,12 +118,13 @@ The themed slide is displayed and updates automatically
118
118
  dismissed or another export starts. Success and error notifications also have
119
119
  a dismiss button and are announced to screen readers. Notifications never
120
120
  appear in exported files or the external presentation window.
121
- - Use **More controls > Output preview** to letterbox the current slide inside
122
- the canvas with
123
- the same fixed 1280×720 typography, spacing, diagram limits, and clipping used
124
- by PDF output. This preview is local to the canvas and does not change deck
125
- state. A visible and accessible warning identifies content that would be
126
- clipped.
121
+ - The canvas starts in **More controls > Output preview**, letterboxing the
122
+ current slide with the same fixed 1280×720 typography, spacing, diagram
123
+ limits, and clipping used by PDF output. Select **Output preview** to return
124
+ to the retained responsive canvas layout. This preview is local to the canvas
125
+ and does not change deck state. A visible and accessible warning identifies
126
+ content that would be clipped. Entering lightweight Architecture editing also
127
+ returns to the responsive layout so its placement controls remain available.
127
128
  - AI should call **`inspect_layout` before exporting a non-scrolling deck**. It
128
129
  renders the currently registered in-memory output snapshot in headless
129
130
  Chromium; it does not read or validate the source file named by `sourceName`.
@@ -426,13 +426,14 @@ function collectDeckLayout(rendered) {
426
426
  function updateFixedPreviewWarning() {
427
427
  const warning = document.getElementById("layoutWarning");
428
428
  const button = document.getElementById("navFixedPreview");
429
- if (!fixedPreviewMode || !layoutTarget) {
429
+ const empty = document.body.classList.contains("markdstage-empty");
430
+ if (!fixedPreviewMode || !layoutTarget || empty) {
430
431
  document.body.classList.remove("fixed-preview-overflow");
431
432
  if (warning) {
432
433
  warning.hidden = true;
433
434
  warning.textContent = "";
434
435
  }
435
- if (button) button.dataset.state = fixedPreviewMode ? "active" : "";
436
+ if (button) button.dataset.state = fixedPreviewMode && !empty ? "active" : "";
436
437
  syncMoreControls();
437
438
  return;
438
439
  }
@@ -3020,6 +3021,7 @@ async function fetchDeck() {
3020
3021
  function setArchitectureEditMode(enabled) {
3021
3022
  const next = Boolean(enabled) && architectureEditAvailable && !presenterMode;
3022
3023
  if (next === architectureEditMode) return false;
3024
+ if (next && fixedPreviewMode) setFixedPreviewMode(false);
3023
3025
  architectureEditMode = next;
3024
3026
  document.body.classList.toggle("architecture-edit-mode", next);
3025
3027
  updateArchitectureEditButton(next);
@@ -4352,6 +4354,17 @@ function init() {
4352
4354
  requestArchitectureEditMode(true);
4353
4355
  }
4354
4356
 
4357
+ // Canvas and CLI preview start on the fixed 16:9 output surface. Presenter
4358
+ // views keep their purpose-built layouts, and the control still lets users
4359
+ // switch back to the responsive canvas layout.
4360
+ if (
4361
+ !presenterMode &&
4362
+ !presenterViewRequested &&
4363
+ params.get("responsive") !== "1"
4364
+ ) {
4365
+ setFixedPreviewMode(true);
4366
+ }
4367
+
4355
4368
  updateArchitectureEditButton();
4356
4369
  if (!previewMode) wireControls();
4357
4370
  else if (navigationEnabled) {
@@ -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
  }