sdocs-dev 1.14.0 → 1.15.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/bin/sdocs-dev.js +2 -0
- package/lib/agent-block.js +3 -3
- package/lib/code-langs.js +8 -2
- package/lib/commands.js +77 -36
- package/lib/help-text.js +193 -5
- package/lib/io.js +57 -3
- package/package.json +1 -1
package/bin/sdocs-dev.js
CHANGED
|
@@ -47,6 +47,8 @@ function buildRouter() {
|
|
|
47
47
|
r.register('schema', { handler: () => { console.log(helpText.SCHEMA); process.exit(0); } });
|
|
48
48
|
r.register('charts', { handler: () => { console.log(helpText.CHARTS_HELP); process.exit(0); } });
|
|
49
49
|
r.register('diagrams', { handler: () => { console.log(helpText.DIAGRAMS_HELP); process.exit(0); } });
|
|
50
|
+
r.register('videos', { handler: () => { console.log(helpText.VIDEOS_HELP); process.exit(0); } });
|
|
51
|
+
r.register('video', { handler: () => { console.log(helpText.VIDEOS_HELP); process.exit(0); } });
|
|
50
52
|
// `sdoc cells` prints the reference; `sdoc cells verify <file>` evaluates a
|
|
51
53
|
// document's tabs headlessly and prints the computed values (the handler
|
|
52
54
|
// calls process.exit with the 0/1/2 result code).
|
package/lib/agent-block.js
CHANGED
|
@@ -18,8 +18,8 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { SETUP_CACHE } = require('./constants');
|
|
20
20
|
|
|
21
|
-
const AGENT_BLOCK_VERSION =
|
|
22
|
-
const AGENT_BLOCK_REASON = '
|
|
21
|
+
const AGENT_BLOCK_VERSION = 13;
|
|
22
|
+
const AGENT_BLOCK_REASON = 'Agent annotations now render as a guided code walkthrough: each note is a callout below its line with a Prev/Next stepper, walked in the order the notes are passed (not line order), and naming several files (sdoc app.py 5:"..." util.py 12:"..." app.py 9:"...") narrates across them as tabs the walk hops between. The `sdoc code` bullet gains this multi-file walkthrough description plus a trigger to build one when the user asks to be walked through code, an MR, a diff, or the current changes. Only the `sdoc code` bullet changed from v12.';
|
|
23
23
|
|
|
24
24
|
const AGENT_BLOCK_BODY = `## SmallDocs
|
|
25
25
|
|
|
@@ -49,7 +49,7 @@ Each command below prints its reference when run with no arguments - run it befo
|
|
|
49
49
|
- \`sdoc diagrams\` - rendering inline Mermaid diagrams (\`\`\`mermaid blocks; has full-screen mode for zoom). Reach for this when drawing system or architectural diagrams (sequence, flow, component layout) - a diagram often communicates the shape of something faster than the equivalent prose.
|
|
50
50
|
- \`sdoc slides\` - inline slide decks (\`\`\`slide / ~~~slide blocks; has full-screen presentation mode). Slides can be standalone exported as \`.pdf\` or \`.pptx\`. \`sdoc present file.md\` - open file directly in fullscreen presentation mode.
|
|
51
51
|
- \`sdoc cells\` - rendering spreadsheets (\`\`\`cells blocks): CSV rows where plain values and =formulas (SUM, AVERAGE, IF, ROUND...) sit in the same grid and compute live. The reader can sort, select ranges for quick stats, edit a scratch copy fullscreen, and download the sheet as Excel (.xlsx) with the formulas still working. Name a block (\`\`\`cells Expenses) to build a workbook of several tabs whose formulas reference each other across sheets (\`=Expenses!B4\`); run \`sdoc cells verify file.md\` to compute the whole workbook headlessly and read the values back. Reach for this when handing the user numbers they will want to check or play with - totals, budgets, projections. \`sdoc report.csv\` opens a CSV file directly as a sheet.
|
|
52
|
-
- \`sdoc code\` - opening a source file or a fenced code block as a syntax-highlighted listing: a light code viewer for reading code with the user away from the IDE. \`sdoc app.rb\` (or \`.js\`, \`.py\`, \`.go\`, \`.rs\`, \`.ts\`...) opens a file as a highlighted listing; a \`\`\`lang fenced block is highlighted inline. Comments in the source get a prominent lane so the code reads clearly top to bottom. The fullscreen view adds a line-number gutter and language-aware folding (collapse a whole method or class); a comment mode lets the user annotate a line or method with review notes, kept in the browser rather than the file. You can also pin your own explanations to lines as you open a file - \`sdoc app.py 22:"
|
|
52
|
+
- \`sdoc code\` - opening a source file or a fenced code block as a syntax-highlighted listing: a light code viewer for reading code with the user away from the IDE. \`sdoc app.rb\` (or \`.js\`, \`.py\`, \`.go\`, \`.rs\`, \`.ts\`...) opens a file as a highlighted listing; a \`\`\`lang fenced block is highlighted inline. Comments in the source get a prominent lane so the code reads clearly top to bottom. The fullscreen view adds a line-number gutter and language-aware folding (collapse a whole method or class); a comment mode lets the user annotate a line or method with review notes, kept in the browser rather than the file. You can also pin your own explanations to lines as you open a file - \`sdoc app.py 22:"the bug is here" 25-28:"wrong comparison"\` - and the file opens as a guided walkthrough: each note is a markdown callout below its line with a Prev / Next stepper, walked in the order you pass the notes (not line order). Name several files to narrate across them - \`sdoc app.py 5:"entry point" util.py 12:"it calls into here" app.py 9:"back here"\` - and each becomes a tab the walkthrough hops between. When the user asks you to walk them through code, an MR, a diff, or the current changes, build one of these. The file rides in the URL like any document; nothing is uploaded. Reach for it when reading or reviewing code with the user, not for prose.
|
|
53
53
|
- \`sdoc schema\` - styling Markdown (fonts, colors, spacing). The default styles are already comfortable to read; reach for this only when they aren't enough - client-facing polish or a bit of fun.
|
|
54
54
|
- \`sdoc feedback\` - rendering interactive elements (\`\`\`form blocks) to receive structured input from the user. Run \`sdoc feedback file.md\` and the user's submission lands as a JSON line on stdout. Good for eliciting complex/subtle feedback. All standard interactive HTML elements with prefilled (but editable) content of your choosing.
|
|
55
55
|
`;
|
package/lib/code-langs.js
CHANGED
|
@@ -72,9 +72,15 @@ function isCodeFile(filePath) {
|
|
|
72
72
|
|
|
73
73
|
// File contents -> a fenced code document. Trailing whitespace is trimmed so a
|
|
74
74
|
// file's final newline doesn't render as an empty last line in the block.
|
|
75
|
-
|
|
75
|
+
//
|
|
76
|
+
// `label` (optional) is appended to the fence info string after the language,
|
|
77
|
+
// e.g. wrapCodeFile(src, 'app.py', 'app.py') -> ```python app.py. A multi-file
|
|
78
|
+
// code walkthrough uses this so the browser can name each tab; a plain single
|
|
79
|
+
// `sdoc app.py` passes no label and the fence stays ```python.
|
|
80
|
+
function wrapCodeFile(raw, filePath, label) {
|
|
76
81
|
var lang = langForFile(filePath);
|
|
77
|
-
|
|
82
|
+
var info = label ? (lang + ' ' + String(label).trim()) : lang;
|
|
83
|
+
return '```' + info + '\n' + String(raw).replace(/\s+$/, '') + '\n```\n';
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
module.exports = {
|
package/lib/commands.js
CHANGED
|
@@ -10,7 +10,7 @@ const { execSync } = require('child_process');
|
|
|
10
10
|
const SDocYaml = require('../shared/sdocs-yaml.js');
|
|
11
11
|
|
|
12
12
|
const { DEFAULT_URL } = require('./constants');
|
|
13
|
-
const { readContent, openBrowser } = require('./io');
|
|
13
|
+
const { readContent, readCodewalkContent, openBrowser } = require('./io');
|
|
14
14
|
const { loadDefaultStyles, applyDefaultStyles, showDefaults, resetDefaults } = require('./styles');
|
|
15
15
|
const { buildUrl } = require('./url');
|
|
16
16
|
const { buildShortUrl } = require('./short-link');
|
|
@@ -28,7 +28,78 @@ async function postCommandHooks() {
|
|
|
28
28
|
// Load content (file or stdin), apply ~/.sdocs/styles.yaml defaults, inject
|
|
29
29
|
// `file:` into front matter, and build either a hash URL or a short URL.
|
|
30
30
|
// Returns { url, contentPresent }.
|
|
31
|
+
// Build a hash URL (or short URL for `share --short`) from finished content.
|
|
32
|
+
// Shared by the single-file and the code-walkthrough paths so both honour
|
|
33
|
+
// `--short`, mode, theme, section, and present identically.
|
|
34
|
+
async function finishUrl(opts, content, local, defaults) {
|
|
35
|
+
if (opts.shortFlag) {
|
|
36
|
+
if (opts.subcommand !== 'share') {
|
|
37
|
+
console.error('sdoc: --short is only valid with the `share` subcommand');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (!content) {
|
|
41
|
+
console.error('sdoc: --short needs content (a file path or piped stdin)');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const url = await buildShortUrl(content, {
|
|
46
|
+
url: opts.url, mode: opts.mode, theme: opts.theme, section: opts.section,
|
|
47
|
+
});
|
|
48
|
+
return { url, contentPresent: !!content };
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error('sdoc: could not create short link -', e.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const url = buildUrl(content, {
|
|
55
|
+
url: opts.url,
|
|
56
|
+
mode: opts.mode,
|
|
57
|
+
theme: opts.theme,
|
|
58
|
+
defaultStyles: !content ? defaults : null,
|
|
59
|
+
section: opts.section,
|
|
60
|
+
local,
|
|
61
|
+
present: opts.present,
|
|
62
|
+
});
|
|
63
|
+
return { url, contentPresent: !!content };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// `sdoc file1.py 4:"..." file2.py 13:"..."` — two or more source files become
|
|
67
|
+
// one code-walkthrough document: a tabbed multi-file view whose annotations
|
|
68
|
+
// step in command order across the tabs. The browser keys off `codewalk: true`
|
|
69
|
+
// in front matter. Front matter carries only basenames, so it is share-safe.
|
|
70
|
+
async function prepareCodewalkUrl(opts) {
|
|
71
|
+
const { body, files } = readCodewalkContent(opts.files);
|
|
72
|
+
|
|
73
|
+
const meta = { codewalk: true, files };
|
|
74
|
+
const anns = (opts.annotations || []).map((a) => {
|
|
75
|
+
// Bind to the cursor file's basename; fall back to the first tab when an
|
|
76
|
+
// annotation was given before any file (or its file dropped out).
|
|
77
|
+
let base = a.file ? path.basename(a.file) : files[0];
|
|
78
|
+
if (files.indexOf(base) === -1) base = files[0];
|
|
79
|
+
return { file: base, line: a.line, endLine: a.endLine, text: a.text };
|
|
80
|
+
});
|
|
81
|
+
if (anns.length) meta.annotations = anns;
|
|
82
|
+
|
|
83
|
+
let content = SDocYaml.serializeFrontMatter(meta) + '\n' + body;
|
|
84
|
+
const defaults = loadDefaultStyles();
|
|
85
|
+
if (defaults) content = applyDefaultStyles(content);
|
|
86
|
+
|
|
87
|
+
// local (the edit-this-file affordance) is single-file today; the
|
|
88
|
+
// walkthrough renders entirely from the shared front matter for now.
|
|
89
|
+
return finishUrl(opts, content, null, defaults);
|
|
90
|
+
}
|
|
91
|
+
|
|
31
92
|
async function prepareUrl(opts) {
|
|
93
|
+
// Annotations render as a walkthrough: a tabbed tour for 2+ files, a single-
|
|
94
|
+
// tab stepper for one. A plain `sdoc app.py` with no annotations stays the
|
|
95
|
+
// ordinary single-file view. Walkthrough order is the order the annotations
|
|
96
|
+
// were given on the command line, not their line order.
|
|
97
|
+
const files = opts.files || [];
|
|
98
|
+
const anns = opts.annotations || [];
|
|
99
|
+
if (files.length > 1 || (files.length >= 1 && anns.length > 0)) {
|
|
100
|
+
return prepareCodewalkUrl(opts);
|
|
101
|
+
}
|
|
102
|
+
|
|
32
103
|
let content = await readContent(opts.file);
|
|
33
104
|
const defaults = loadDefaultStyles();
|
|
34
105
|
if (content && defaults) {
|
|
@@ -45,7 +116,10 @@ async function prepareUrl(opts) {
|
|
|
45
116
|
let changed = false;
|
|
46
117
|
if (!parsed.meta.file) { parsed.meta.file = path.basename(opts.file); changed = true; }
|
|
47
118
|
if (opts.annotations && opts.annotations.length) {
|
|
48
|
-
|
|
119
|
+
// A single file needs no per-annotation `file` binding — drop it so the
|
|
120
|
+
// serialized shape stays {line, endLine, text}. (Multi-file keeps it, in
|
|
121
|
+
// prepareCodewalkUrl.)
|
|
122
|
+
parsed.meta.annotations = opts.annotations.map(({ file, ...rest }) => rest);
|
|
49
123
|
changed = true;
|
|
50
124
|
}
|
|
51
125
|
if (changed) {
|
|
@@ -65,40 +139,7 @@ async function prepareUrl(opts) {
|
|
|
65
139
|
}
|
|
66
140
|
}
|
|
67
141
|
|
|
68
|
-
|
|
69
|
-
if (opts.shortFlag) {
|
|
70
|
-
if (opts.subcommand !== 'share') {
|
|
71
|
-
console.error('sdoc: --short is only valid with the `share` subcommand');
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
if (!content) {
|
|
75
|
-
console.error('sdoc: --short needs content (a file path or piped stdin)');
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
url = await buildShortUrl(content, {
|
|
80
|
-
url: opts.url,
|
|
81
|
-
mode: opts.mode,
|
|
82
|
-
theme: opts.theme,
|
|
83
|
-
section: opts.section,
|
|
84
|
-
});
|
|
85
|
-
} catch (e) {
|
|
86
|
-
console.error('sdoc: could not create short link -', e.message);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
url = buildUrl(content, {
|
|
91
|
-
url: opts.url,
|
|
92
|
-
mode: opts.mode,
|
|
93
|
-
theme: opts.theme,
|
|
94
|
-
defaultStyles: !content ? defaults : null,
|
|
95
|
-
section: opts.section,
|
|
96
|
-
local,
|
|
97
|
-
present: opts.present,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return { url, contentPresent: !!content };
|
|
142
|
+
return finishUrl(opts, content, local, defaults);
|
|
102
143
|
}
|
|
103
144
|
|
|
104
145
|
// Default flow: `sdoc <file>` or `sdoc` (no args, or piped stdin).
|
package/lib/help-text.js
CHANGED
|
@@ -21,6 +21,7 @@ USAGE
|
|
|
21
21
|
sdoc schema Print the full styles schema
|
|
22
22
|
sdoc charts Chart types, options, and styling guide
|
|
23
23
|
sdoc diagrams Mermaid diagrams reference (\`\`\`mermaid blocks)
|
|
24
|
+
sdoc videos YouTube embed reference (\`\`\`video blocks)
|
|
24
25
|
sdoc cells Inline spreadsheet reference (\`\`\`cells blocks)
|
|
25
26
|
sdoc code Syntax highlighting + code-viewer reference
|
|
26
27
|
sdoc app.rb / server.js / ... Open a source file as a highlighted listing
|
|
@@ -928,6 +929,55 @@ EXAMPLE
|
|
|
928
929
|
`;
|
|
929
930
|
|
|
930
931
|
|
|
932
|
+
const VIDEOS_HELP = `
|
|
933
|
+
SmallDocs - Videos
|
|
934
|
+
==================
|
|
935
|
+
Embed a YouTube video in markdown using a \`\`\`video fenced code block.
|
|
936
|
+
The body is a YouTube URL (any common shape) or a bare 11-character
|
|
937
|
+
video id, with optional \`title:\` and \`start:\` lines.
|
|
938
|
+
|
|
939
|
+
BASIC SYNTAX
|
|
940
|
+
\`\`\`video
|
|
941
|
+
https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
942
|
+
\`\`\`
|
|
943
|
+
|
|
944
|
+
Bare id, short link, embed link and shorts link all work:
|
|
945
|
+
\`\`\`video
|
|
946
|
+
dQw4w9WgXcQ
|
|
947
|
+
\`\`\`
|
|
948
|
+
|
|
949
|
+
WITH A TITLE AND START TIME
|
|
950
|
+
\`\`\`video
|
|
951
|
+
https://youtu.be/dQw4w9WgXcQ
|
|
952
|
+
title: Our launch walkthrough
|
|
953
|
+
start: 1:30
|
|
954
|
+
\`\`\`
|
|
955
|
+
\`start:\` accepts seconds (90), clock (1:30, 1:02:03) or 1m30s / 1h2m3s.
|
|
956
|
+
A \`t=\` parameter already on the URL is used when no \`start:\` is given.
|
|
957
|
+
\`title:\` sets the iframe title (accessibility) and a caption link.
|
|
958
|
+
|
|
959
|
+
WHAT RENDERS
|
|
960
|
+
A responsive 16:9 player that scales with the body column. The embed
|
|
961
|
+
host is youtube-nocookie.com, so no tracking cookie is set until the
|
|
962
|
+
viewer presses play. \`rel=0\` is applied, which limits the end-screen
|
|
963
|
+
suggestions to the source channel (YouTube no longer allows fully
|
|
964
|
+
removing them from an embed).
|
|
965
|
+
|
|
966
|
+
LIMITS
|
|
967
|
+
- Per-block source cap: 8 KB.
|
|
968
|
+
- Per-document video cap: 50 (excess rendered as plain code).
|
|
969
|
+
|
|
970
|
+
SECURITY
|
|
971
|
+
No iframe ever travels through the document markup - SmallDocs still
|
|
972
|
+
strips iframes from ordinary markdown. The renderer reads only the
|
|
973
|
+
block text, extracts the 11-character video id, validates it against
|
|
974
|
+
\`[A-Za-z0-9_-]{11}\`, and builds the player from a src it constructs
|
|
975
|
+
itself. Anything that is not a recognised YouTube URL or a valid id
|
|
976
|
+
renders as an inline error, never an embed. Treat block source as
|
|
977
|
+
untrusted - it travels in the URL hash with the rest of the document.
|
|
978
|
+
`;
|
|
979
|
+
|
|
980
|
+
|
|
931
981
|
const CELLS_HELP = `
|
|
932
982
|
SmallDocs - Cells (sheets)
|
|
933
983
|
======================
|
|
@@ -1266,6 +1316,20 @@ navigate.
|
|
|
1266
1316
|
template), keep these in mind - they're the difference between a
|
|
1267
1317
|
deck that lands and one that doesn't.
|
|
1268
1318
|
|
|
1319
|
+
Templates for scratch, custom layout for anything seen. The
|
|
1320
|
+
built-in templates are the fast path: legible, consistent, and
|
|
1321
|
+
well suited to fast information transfer - an internal review, a
|
|
1322
|
+
working draft, a deck you will talk over. They are not built for
|
|
1323
|
+
an audience that cares about feel, so a deck made only from them
|
|
1324
|
+
reads as functional rather than designed. For anything
|
|
1325
|
+
external-facing - a client, a conference, a launch, anything with
|
|
1326
|
+
your name on it that you will not be in the room to narrate -
|
|
1327
|
+
compose the deck from raw shapes instead. If you are not sure which
|
|
1328
|
+
setting you are in, ask your user, explaining the trade-off:
|
|
1329
|
+
templates are faster and uniform, raw shapes take longer but let
|
|
1330
|
+
the deck carry a designed look. See \`sdoc slides custom-shapes\`
|
|
1331
|
+
for the shape vocabulary and the design principles that go with it.
|
|
1332
|
+
|
|
1269
1333
|
Margins. Keep all content inside a 1-unit safe area on every side
|
|
1270
1334
|
of a 16x9 grid (so x ∈ [1, 15], y ∈ [0.5, 8.5]). Nothing touches
|
|
1271
1335
|
the slide edge except a deliberate full-bleed background (\`section\`
|
|
@@ -1316,9 +1380,54 @@ navigate.
|
|
|
1316
1380
|
A bulleted paragraph is hiding the fact that you haven't decided
|
|
1317
1381
|
what you're claiming.
|
|
1318
1382
|
|
|
1319
|
-
When
|
|
1320
|
-
|
|
1321
|
-
registry.
|
|
1383
|
+
When the deck is internal or you only need it to read clearly,
|
|
1384
|
+
\`@extends\` a built-in template instead of composing from raw
|
|
1385
|
+
shapes - run \`sdoc slides list\` to see the registry. Reach for
|
|
1386
|
+
raw shapes when the deck will be seen by an audience and the
|
|
1387
|
+
default beat is not enough; the guidelines above are what keep
|
|
1388
|
+
that hand-built deck on the professional side of the line. If a
|
|
1389
|
+
custom layout repeats across slides, define it once as your own
|
|
1390
|
+
\`@template\` and \`@extends\` it - the consistency of a template
|
|
1391
|
+
with a look you designed.
|
|
1392
|
+
|
|
1393
|
+
\u2500\u2500 VERIFYING A DECK (OPTIONAL, BUT IT IS WHAT SEPARATES CLEAN FROM BROKEN) \u2500\u2500
|
|
1394
|
+
You are authoring slides you cannot see. You place coordinates and
|
|
1395
|
+
trust them, and the renderer does not warn you when a shape covers
|
|
1396
|
+
text, when a label wraps to a line you did not intend, when text sits
|
|
1397
|
+
on a fill it cannot be read against, or when an arrow lands in empty
|
|
1398
|
+
space. Templates mostly avoid this; hand-placed raw shapes do not.
|
|
1399
|
+
The single most effective check is to look at the rendered result and
|
|
1400
|
+
fix what you see.
|
|
1401
|
+
|
|
1402
|
+
If you have a way to render a page to an image - a headless browser
|
|
1403
|
+
you can drive (Playwright or Puppeteer in headless mode, a headless
|
|
1404
|
+
system Chrome), or any screenshot tool in your environment - use it.
|
|
1405
|
+
Open the deck, capture the slides, look, fix the DSL, and render
|
|
1406
|
+
again. Two or three passes turns a deck with hidden text and clipped
|
|
1407
|
+
labels into one that reads cleanly. When you render, check each slide
|
|
1408
|
+
for:
|
|
1409
|
+
- text covered by a shape drawn after it (source order is paint order)
|
|
1410
|
+
- text with poor contrast against whatever is actually painted under it
|
|
1411
|
+
- labels that wrap or overflow their box
|
|
1412
|
+
- connectors (l / a) that float short of, or pierce, what they join
|
|
1413
|
+
- a composition that sits low, high, or lopsided in the frame
|
|
1414
|
+
|
|
1415
|
+
The CLI does not bundle a browser - it has no runtime dependencies on
|
|
1416
|
+
purpose - so this check is something you bring from your environment,
|
|
1417
|
+
not something sdoc installs. If you do not have a headless browser and
|
|
1418
|
+
you judge that rendering would catch real problems, you can ask your
|
|
1419
|
+
user to install one (for example \`npx playwright install chromium\`, or
|
|
1420
|
+
to point you at a browser already on the machine) and explain why: a
|
|
1421
|
+
render check catches the text-overlap, contrast, and wrapping faults
|
|
1422
|
+
that are invisible in the markdown source. Leave the decision to them;
|
|
1423
|
+
it is their machine, and the subtleties of what is available are yours
|
|
1424
|
+
to work out in context.
|
|
1425
|
+
|
|
1426
|
+
\`sdoc present <file>\` opens the deck fullscreen for a human to review,
|
|
1427
|
+
which is the right check when a person is in the loop. If you cannot
|
|
1428
|
+
render at all, keep each slide simple enough to verify by arithmetic -
|
|
1429
|
+
list every text shape's box and confirm no later shape's fill overlaps
|
|
1430
|
+
it - and prefer templates for anything you will not be able to see.
|
|
1322
1431
|
|
|
1323
1432
|
\u2500\u2500 FENCE SYNTAX \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1324
1433
|
Wrap shape DSL in a ~~~slide fenced block:
|
|
@@ -1845,7 +1954,8 @@ navigate.
|
|
|
1845
1954
|
errors + the full slide source — for pasting back to an agent.
|
|
1846
1955
|
|
|
1847
1956
|
Common errors:
|
|
1848
|
-
- "shape extends outside grid WxH"
|
|
1957
|
+
- "shape extends outside grid WxH" bbox past an edge by >10% of the
|
|
1958
|
+
grid (a little bleed is allowed)
|
|
1849
1959
|
- \`unknown id "@name"\` @-ref before the shape is declared
|
|
1850
1960
|
- \`duplicate id "#name"\` two shapes share an id
|
|
1851
1961
|
- \`invalid attribute key\` key must start with a letter
|
|
@@ -1969,6 +2079,49 @@ rather than as designed.
|
|
|
1969
2079
|
default. Spend visual weight only on the one or two things the
|
|
1970
2080
|
slide is about. Empty space is half the design.
|
|
1971
2081
|
|
|
2082
|
+
Text alignment.
|
|
2083
|
+
Except for slides with very few elements, or custom shapes with
|
|
2084
|
+
very few words, prefer left-aligned text throughout a slide.
|
|
2085
|
+
Left-aligning most elements - particularly any with more than
|
|
2086
|
+
three words - creates a calm, confident slide that is easy to
|
|
2087
|
+
follow.
|
|
2088
|
+
|
|
2089
|
+
An implicit grid.
|
|
2090
|
+
Take inspiration from Josef Muller-Brockmann's work on grid
|
|
2091
|
+
systems. Consistency of alignment between elements gives off the
|
|
2092
|
+
feeling of considered, structured thinking. Unless you are
|
|
2093
|
+
creating something specific from custom shapes, prefer to build
|
|
2094
|
+
an implicit grid on the slide. A presentation feels even more
|
|
2095
|
+
professional when that grid language flows from slide to slide.
|
|
2096
|
+
|
|
2097
|
+
Branding, borders and background.
|
|
2098
|
+
Less is often more. Precise layouts are hard with custom shapes:
|
|
2099
|
+
overlapping positions may not land where you expect, and you
|
|
2100
|
+
cannot easily check how the slide feels. So when you give a slide
|
|
2101
|
+
a mood through coloured background shapes, keep it relatively
|
|
2102
|
+
simple. This should not stop you being bold, but design with a
|
|
2103
|
+
few key shapes (which may overlap), not many overlapping ones.
|
|
2104
|
+
|
|
2105
|
+
Contrast and legibility.
|
|
2106
|
+
Consider the colour of text against the colour of whatever falls
|
|
2107
|
+
directly beneath it. Readers should be able to calmly take text
|
|
2108
|
+
in from a slide, so ensure there is healthy contrast, at least,
|
|
2109
|
+
between text and the colour beneath it. In general, try to
|
|
2110
|
+
overlay text on a single patch of colour (or very closely
|
|
2111
|
+
related colours) to keep it straightforwardly legible.
|
|
2112
|
+
|
|
2113
|
+
Attempt to verify what you cannot see.
|
|
2114
|
+
The principles above guide the layout, but a hand-placed shape
|
|
2115
|
+
can still cover text, a label can wrap, or an arrow can miss its
|
|
2116
|
+
target, and none of it shows in the source. If you can drive a
|
|
2117
|
+
headless browser or any screenshot tool in your environment,
|
|
2118
|
+
render the deck and fix what you see before trusting it. If you
|
|
2119
|
+
cannot, it is worth raising with your user - for example, "I
|
|
2120
|
+
would like to use Playwright to verify the slides render
|
|
2121
|
+
correctly, is that okay?" - since a quick render check catches
|
|
2122
|
+
problems the markdown cannot show. See the VERIFYING A DECK
|
|
2123
|
+
section of \`sdoc slides\` for what to look for.
|
|
2124
|
+
|
|
1972
2125
|
── SHAPE KINDS ───────────────────────────────────────
|
|
1973
2126
|
r x y w h rectangle (x,y = top-left; w,h = size)
|
|
1974
2127
|
i x y w h image rect (sugar for \`r\` with \`image=\`; see IMAGES)
|
|
@@ -2069,6 +2222,12 @@ rather than as designed.
|
|
|
2069
2222
|
so the layout doesn't reflow when
|
|
2070
2223
|
icons swap in.
|
|
2071
2224
|
|
|
2225
|
+
Pair an icon with its label as two separate shapes - the icon in
|
|
2226
|
+
its own small box, the text in a shape beside or below it. Don't
|
|
2227
|
+
put \`|\` text on an icon (it overlaps the glyph) or drop an icon
|
|
2228
|
+
onto a text shape. Keep every icon in a set the same size (w = h)
|
|
2229
|
+
on a shared row.
|
|
2230
|
+
|
|
2072
2231
|
Arrow geometry: the coordinates are the line's centerline. The head is
|
|
2073
2232
|
symmetric around the line, extending up to 3 * strokeWidth perpendicular
|
|
2074
2233
|
on each side. So a horizontal arrow at y=5.78 with strokeWidth=0.06
|
|
@@ -2279,6 +2438,18 @@ rather than as designed.
|
|
|
2279
2438
|
The three cards then read as a parallel set rather than three
|
|
2280
2439
|
improvisations.
|
|
2281
2440
|
|
|
2441
|
+
5. Icons collide with text when they share a box.
|
|
2442
|
+
|
|
2443
|
+
The \`icon\` shape paints its glyph across its whole bbox, so
|
|
2444
|
+
text in the same box - or a text shape the icon sits on top of
|
|
2445
|
+
- overlaps it. Keep them as two shapes on one row:
|
|
2446
|
+
|
|
2447
|
+
icon 8 20 4 4 name=recycle color=#0165a5
|
|
2448
|
+
r 13 20 26 4 text=body align=left valign=center | Repairable by design
|
|
2449
|
+
|
|
2450
|
+
Icon in its own 4x4 box; the label starts after it at x=13,
|
|
2451
|
+
centred on the same y. Size every icon in a set the same.
|
|
2452
|
+
|
|
2282
2453
|
── SHAPE ATTRIBUTES ──────────────────────────────────
|
|
2283
2454
|
Between geometry and \`|\`:
|
|
2284
2455
|
|
|
@@ -2831,6 +3002,23 @@ ANNOTATIONS (walk someone through the code)
|
|
|
2831
3002
|
an agent explaining code to the reader; the in-browser comment mode
|
|
2832
3003
|
(--comment) is the reverse - the reader's own review notes.
|
|
2833
3004
|
|
|
3005
|
+
Annotations render as a WALKTHROUGH: the file opens in the fullscreen viewer
|
|
3006
|
+
with each note carrying a Prev / Next stepper, so the reader is guided from
|
|
3007
|
+
one note to the next (arrow keys work too; a restart button returns to the
|
|
3008
|
+
start). The steps follow the ORDER YOU PASS THEM, not line order:
|
|
3009
|
+
|
|
3010
|
+
sdoc app.py 10:"start here" 5:"then jump back up here"
|
|
3011
|
+
|
|
3012
|
+
walks to line 10 first, then line 5.
|
|
3013
|
+
|
|
3014
|
+
Pass several files to narrate across them - each becomes a tab, and a step
|
|
3015
|
+
in another file switches to its tab as you walk (a small "-> file" marker
|
|
3016
|
+
shows when a step crosses files):
|
|
3017
|
+
|
|
3018
|
+
sdoc app.py 5:"entry point" util.py 12:"it calls into here" app.py 9:"back"
|
|
3019
|
+
|
|
3020
|
+
Naming a file twice gives one tab; the walk still hops between them in order.
|
|
3021
|
+
|
|
2834
3022
|
COMMENTS
|
|
2835
3023
|
Comments are deliberately prominent - italic, full-contrast colour, a faint
|
|
2836
3024
|
tint - rather than the usual muted grey, so the human explanation in a
|
|
@@ -2847,4 +3035,4 @@ SUPPORTED LANGUAGES
|
|
|
2847
3035
|
they appear. An unknown language label renders as plain text.
|
|
2848
3036
|
`;
|
|
2849
3037
|
|
|
2850
|
-
module.exports = { HELP, COMMENTS_HELP, SCHEMA, CHARTS_HELP, DIAGRAMS_HELP, CELLS_HELP, CODE_HELP, SLIDES_HELP, SLIDES_CUSTOM_SHAPES_HELP, LIBRARY_HELP };
|
|
3038
|
+
module.exports = { HELP, COMMENTS_HELP, SCHEMA, CHARTS_HELP, DIAGRAMS_HELP, VIDEOS_HELP, CELLS_HELP, CODE_HELP, SLIDES_HELP, SLIDES_CUSTOM_SHAPES_HELP, LIBRARY_HELP };
|
package/lib/io.js
CHANGED
|
@@ -5,10 +5,11 @@ const path = require('path');
|
|
|
5
5
|
const { execFileSync } = require('child_process');
|
|
6
6
|
const { transcludeCells } = require('./cells-transclude');
|
|
7
7
|
const { isWrappedFile, wrapForDisplay } = require('./file-wrap');
|
|
8
|
+
const codeLangs = require('./code-langs');
|
|
8
9
|
|
|
9
10
|
const SUBCOMMANDS = new Set([
|
|
10
11
|
'new', 'share', 'schema', 'defaults', 'help', 'version',
|
|
11
|
-
'charts', 'diagrams', 'cells', 'code', 'comments',
|
|
12
|
+
'charts', 'diagrams', 'videos', 'video', 'cells', 'code', 'comments',
|
|
12
13
|
'setup', 'safe', 'auto-update', 'refresh', 'upgrade',
|
|
13
14
|
'bridge', 'feedback',
|
|
14
15
|
'slides', 'present',
|
|
@@ -56,6 +57,13 @@ function parseArgs(argv) {
|
|
|
56
57
|
let sheetName = null;
|
|
57
58
|
const addTags = [];
|
|
58
59
|
const annotations = [];
|
|
60
|
+
// Multi-file code walkthrough: every source-code positional is collected
|
|
61
|
+
// into `files`, in command order. `currentFile` is the cursor an annotation
|
|
62
|
+
// binds to, so `file1.py 4:"x" file2.py 13:"y"` ties line 4 to file1 and
|
|
63
|
+
// line 13 to file2. The first code file also fills the single-file `file`
|
|
64
|
+
// slot, so a one-file `sdoc app.py` is unchanged.
|
|
65
|
+
const files = [];
|
|
66
|
+
let currentFile = null;
|
|
59
67
|
|
|
60
68
|
for (let i = 0; i < args.length; i++) {
|
|
61
69
|
const arg = args[i];
|
|
@@ -127,11 +135,24 @@ function parseArgs(argv) {
|
|
|
127
135
|
// Strip one layer of surrounding quotes if a shell preserved them.
|
|
128
136
|
const text = ann[3].replace(/^"([\s\S]*)"$/, '$1').replace(/^'([\s\S]*)'$/, '$1');
|
|
129
137
|
if (start >= 1 && end >= start && text.trim()) {
|
|
130
|
-
|
|
138
|
+
// `file` binds the annotation to the most-recently-named code file
|
|
139
|
+
// (null if none yet — resolved to the only/first file downstream).
|
|
140
|
+
annotations.push({ line: start, endLine: end, text, file: currentFile });
|
|
131
141
|
}
|
|
132
142
|
continue;
|
|
133
143
|
}
|
|
134
144
|
|
|
145
|
+
// A source-code positional (in the default flow, not under a subcommand):
|
|
146
|
+
// collect it as a walkthrough file and move the annotation cursor onto it.
|
|
147
|
+
// Subcommand sub-args (e.g. `slides icons`) are never code files, so they
|
|
148
|
+
// fall through to the file/extra slots below as before.
|
|
149
|
+
if (!subcommand && codeLangs.isCodeFile(arg)) {
|
|
150
|
+
files.push(arg);
|
|
151
|
+
currentFile = arg;
|
|
152
|
+
if (!file) file = arg;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
if (!file) { file = arg; continue; }
|
|
136
157
|
// Second positional is captured as `extra` so `sdoc slides icons heart`
|
|
137
158
|
// gets {subcommand: 'slides', file: 'icons', extra: 'heart'}.
|
|
@@ -144,7 +165,7 @@ function parseArgs(argv) {
|
|
|
144
165
|
messageText, connectTimeoutS, idleTimeoutS, reconnectGraceMs,
|
|
145
166
|
keepOpenFlag, logFile,
|
|
146
167
|
tagsFlag, helpFlag, yesFlag, dryRunFlag, sheetName,
|
|
147
|
-
addTags, annotations,
|
|
168
|
+
addTags, annotations, files,
|
|
148
169
|
};
|
|
149
170
|
}
|
|
150
171
|
|
|
@@ -183,6 +204,38 @@ async function readContent(file) {
|
|
|
183
204
|
return null; // no content — just open studio
|
|
184
205
|
}
|
|
185
206
|
|
|
207
|
+
// Read N source files into one code-walkthrough body: each unique file wrapped
|
|
208
|
+
// in a ```<lang> <basename> fence, joined in command order. Returns the body
|
|
209
|
+
// plus the de-duplicated basename list (the tab order). Tabs are keyed by
|
|
210
|
+
// basename — a file named twice on the command line is one tab; two DIFFERENT
|
|
211
|
+
// files sharing a basename is an error rather than a silent merge (and keeps
|
|
212
|
+
// the shared front matter to safe basenames, matching the single-file `file:`).
|
|
213
|
+
function readCodewalkContent(files) {
|
|
214
|
+
const parts = [];
|
|
215
|
+
const tabs = [];
|
|
216
|
+
const byBase = Object.create(null);
|
|
217
|
+
for (const f of files) {
|
|
218
|
+
const resolved = path.resolve(f);
|
|
219
|
+
const base = path.basename(f);
|
|
220
|
+
if (byBase[base]) {
|
|
221
|
+
if (byBase[base] !== resolved) {
|
|
222
|
+
console.error(`sdoc: a code walkthrough needs distinct file names — two files named "${base}"`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
continue; // same file referenced again → one tab
|
|
226
|
+
}
|
|
227
|
+
if (!fs.existsSync(resolved)) {
|
|
228
|
+
console.error(`sdoc: file not found: ${f}`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
byBase[base] = resolved;
|
|
232
|
+
tabs.push(base);
|
|
233
|
+
const raw = fs.readFileSync(resolved, 'utf-8');
|
|
234
|
+
parts.push(codeLangs.wrapCodeFile(raw, f, base));
|
|
235
|
+
}
|
|
236
|
+
return { body: parts.join('\n'), files: tabs };
|
|
237
|
+
}
|
|
238
|
+
|
|
186
239
|
function openBrowser(url) {
|
|
187
240
|
try {
|
|
188
241
|
if (process.platform === 'darwin') execFileSync('open', [url]);
|
|
@@ -197,5 +250,6 @@ module.exports = {
|
|
|
197
250
|
SUBCOMMANDS,
|
|
198
251
|
parseArgs,
|
|
199
252
|
readContent,
|
|
253
|
+
readCodewalkContent,
|
|
200
254
|
openBrowser,
|
|
201
255
|
};
|