sdocs-dev 1.6.2 → 1.13.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-bridge.js +974 -0
- package/bin/sdocs-dev.js +152 -2102
- package/bin/sdocs-icon-names.js +1965 -0
- package/lib/agent-block.js +245 -0
- package/lib/agent-files.js +162 -0
- package/lib/bridge-commands.js +171 -0
- package/lib/cells-transclude.js +114 -0
- package/lib/cells-verify.js +165 -0
- package/lib/commands.js +291 -0
- package/lib/constants.js +283 -0
- package/lib/help-text.js +2759 -0
- package/lib/io.js +177 -0
- package/lib/library-autostart.js +145 -0
- package/lib/library-commands.js +307 -0
- package/lib/library-ephemeral.js +111 -0
- package/lib/library-index.js +285 -0
- package/lib/library-paths.js +20 -0
- package/lib/library-scan.js +258 -0
- package/lib/library-server.js +400 -0
- package/lib/library-store.js +141 -0
- package/lib/router.js +52 -0
- package/lib/safe.js +200 -0
- package/lib/setup.js +351 -0
- package/lib/short-link.js +105 -0
- package/lib/styles.js +91 -0
- package/lib/update-check.js +163 -0
- package/lib/url.js +111 -0
- package/package.json +5 -16
- package/shared/sdocs-cells-formula.js +485 -0
- package/shared/sdocs-cells.js +389 -0
- package/shared/sdocs-contrast.js +196 -0
- package/shared/sdocs-form-block.js +605 -0
- package/shared/sdocs-library-tags.js +41 -0
- package/{public → shared}/sdocs-styles.js +134 -5
- package/README.md +0 -149
- /package/{public → shared}/sdocs-slugify.js +0 -0
- /package/{public → shared}/sdocs-yaml.js +0 -0
package/lib/help-text.js
ADDED
|
@@ -0,0 +1,2759 @@
|
|
|
1
|
+
// Long help strings printed by `sdoc help`, `sdoc schema`, `sdoc charts`,
|
|
2
|
+
// `sdoc diagrams`, `sdoc comments`. Kept as plain data with no logic.
|
|
3
|
+
|
|
4
|
+
const HELP = `
|
|
5
|
+
SmallDocs CLI
|
|
6
|
+
=========
|
|
7
|
+
Open, share, and style markdown files from the terminal.
|
|
8
|
+
|
|
9
|
+
USAGE
|
|
10
|
+
sdoc <file> Open file in browser (read mode, no live link)
|
|
11
|
+
sdoc <file> --write Open in write mode
|
|
12
|
+
sdoc <file> --style Open with style panel
|
|
13
|
+
sdoc <file> --raw Open raw markdown source
|
|
14
|
+
sdoc <file> --comment Open in comment mode (review/annotate)
|
|
15
|
+
sdoc bridge <file> Live editing session: edits autosave to disk,
|
|
16
|
+
external changes push to the page (see LIVE BRIDGE)
|
|
17
|
+
sdoc new New blank document (write mode)
|
|
18
|
+
sdoc share <file> Copy shareable link to clipboard
|
|
19
|
+
sdoc share <file> --section "X" Link with section anchor
|
|
20
|
+
sdoc share <file> --short Encrypted /s/<id> short link (see SHORT LINKS)
|
|
21
|
+
sdoc schema Print the full styles schema
|
|
22
|
+
sdoc charts Chart types, options, and styling guide
|
|
23
|
+
sdoc diagrams Mermaid diagrams reference (\`\`\`mermaid blocks)
|
|
24
|
+
sdoc cells Inline spreadsheet reference (\`\`\`cells blocks)
|
|
25
|
+
sdoc color-analysis <file> Check custom colours for readable contrast
|
|
26
|
+
(both themes). Run after styling a doc.
|
|
27
|
+
sdoc comments Comment-format reference (for agents)
|
|
28
|
+
sdoc feedback Interactive form DSL reference (\`\`\`form blocks)
|
|
29
|
+
sdoc feedback <file> Open <file> for the user to fill in; exits on first submit
|
|
30
|
+
sdoc feedback <file> --keep-open Stay alive across many submits; tail stdout per click
|
|
31
|
+
sdoc slides Slide DSL reference (grids, shapes, content)
|
|
32
|
+
sdoc slides list Built-in slide template registry + slot names
|
|
33
|
+
sdoc slides icons [query] Lucide icon names available to the \`icon\` shape kind
|
|
34
|
+
sdoc slides custom-shapes Raw-shape primitives + design principles reference
|
|
35
|
+
sdoc present <file> Open <file> and jump straight into fullscreen slides
|
|
36
|
+
sdoc library Open the personal markdown library at smalldocs.org/library
|
|
37
|
+
sdoc library ls List markdown indexed in this project (walks up to .git)
|
|
38
|
+
sdoc library ls --tags Tag bag (tag - count) for this project
|
|
39
|
+
sdoc library --help Full library reference - commands, tagging, rescue, autostart
|
|
40
|
+
sdoc <file> +tag1 +tag2 Inject tags into the file's front matter at open time
|
|
41
|
+
sdoc defaults Show ~/.sdocs/styles.yaml
|
|
42
|
+
sdoc defaults --reset Remove default styles
|
|
43
|
+
sdoc setup Wire SmallDocs into your coding agents
|
|
44
|
+
sdoc refresh Update the SmallDocs section in agent files to the current version
|
|
45
|
+
sdoc auto-update [on|off] Toggle auto-install of sdoc updates
|
|
46
|
+
sdoc upgrade Upgrade sdoc to the latest version now
|
|
47
|
+
sdoc safe Verify the SmallDocs server is running the published code
|
|
48
|
+
sdoc safe --json Same, machine-readable (for agents)
|
|
49
|
+
sdoc safe --audit Same, plus GitHub links to server-side source files
|
|
50
|
+
sdoc help Show this help
|
|
51
|
+
sdoc --version Print the installed version
|
|
52
|
+
cat file.md | sdoc Pipe markdown from stdin
|
|
53
|
+
cat file.md | sdoc share Pipe to clipboard link
|
|
54
|
+
|
|
55
|
+
MODE FLAGS
|
|
56
|
+
--read Clean reading view (default when file given)
|
|
57
|
+
--write Opens the contentEditable writer
|
|
58
|
+
--style Styled preview with style panel visible
|
|
59
|
+
--raw Shows raw markdown source
|
|
60
|
+
--comment Comment mode: gutter buttons appear on each block; cards
|
|
61
|
+
render under blocks that already have comments. Useful both
|
|
62
|
+
for human review and for opening files an agent has annotated.
|
|
63
|
+
|
|
64
|
+
OPTIONS
|
|
65
|
+
--section <heading> Scroll to heading section on load
|
|
66
|
+
--light Open in light theme
|
|
67
|
+
--dark Open in dark theme
|
|
68
|
+
--url <base> Custom base URL (default: https://smalldocs.org)
|
|
69
|
+
--mode <m> Alias for --read / --write / --style / --raw / --comment
|
|
70
|
+
--short Use the encrypted /s/<id> short-URL form (share
|
|
71
|
+
subcommand only). See SHORT LINKS below.
|
|
72
|
+
--json Machine-readable output (safe subcommand only).
|
|
73
|
+
--audit Also print GitHub links to server-side source
|
|
74
|
+
files (safe subcommand only).
|
|
75
|
+
--keep-open feedback subcommand: keep the bridge alive across
|
|
76
|
+
many submits instead of exiting on the first one.
|
|
77
|
+
--log-file <path> feedback subcommand: append one JSON line per
|
|
78
|
+
submit to <path> (mirror of stdout, for harnesses
|
|
79
|
+
that can't tail a background process).
|
|
80
|
+
--message <text> feedback subcommand: show <text> as a banner
|
|
81
|
+
above the document.
|
|
82
|
+
|
|
83
|
+
ENVIRONMENT
|
|
84
|
+
SDOCS_URL Fallback base URL if --url is not passed.
|
|
85
|
+
|
|
86
|
+
INTERACTIVE FEEDBACK (sdoc feedback)
|
|
87
|
+
An agent writes a fenced \`\`\`form block into a markdown file and runs
|
|
88
|
+
\`sdoc feedback file.md\`. The browser renders real form controls
|
|
89
|
+
(radio, checkbox, select, text, textarea, number, date). When the
|
|
90
|
+
user clicks a submit button:
|
|
91
|
+
|
|
92
|
+
- the bridge writes their answers into the same file
|
|
93
|
+
(under \`answers:\` and \`submissions:\` inside the form block)
|
|
94
|
+
- one JSON line lands on stdout: {event, by, at, scope, values, final}
|
|
95
|
+
- in single-shot mode (no --keep-open) the process exits 0
|
|
96
|
+
- in --keep-open mode the bridge stays alive for the next click
|
|
97
|
+
|
|
98
|
+
Run \`sdoc feedback\` (no args) for the full DSL reference: field
|
|
99
|
+
types, button options, the multi-round flow, and how agents on
|
|
100
|
+
different harnesses should consume the events.
|
|
101
|
+
|
|
102
|
+
LIVE BRIDGE (sdoc bridge)
|
|
103
|
+
\`sdoc <file>\` renders a snapshot: the document travels in the URL and
|
|
104
|
+
opens read-only-by-default. Nothing connects back to disk, so it works
|
|
105
|
+
everywhere with no prompts. Use this to read, share, or hand off a file.
|
|
106
|
+
|
|
107
|
+
\`sdoc bridge <file>\` opens a live session instead. The CLI starts a
|
|
108
|
+
local server on 127.0.0.1 and the browser connects to it, so edits in
|
|
109
|
+
the page autosave to the file on disk and external edits push to the
|
|
110
|
+
page. The terminal stays parked until you close the tab or press
|
|
111
|
+
Ctrl-C, so run it in the background when you want to keep working.
|
|
112
|
+
|
|
113
|
+
The first time the page connects, the browser asks permission to talk
|
|
114
|
+
to a local process (Chrome calls this "Apps on device" / Local Network
|
|
115
|
+
Access). The user has to accept, or the page stays read-only. Safari
|
|
116
|
+
does not allow the loopback connection at all; there the page stays on
|
|
117
|
+
the read-only snapshot.
|
|
118
|
+
|
|
119
|
+
FILE INFO CARD
|
|
120
|
+
When you \`sdoc <file>\`, the browser shows a small info card
|
|
121
|
+
above the document with:
|
|
122
|
+
file The filename — included in the share URL.
|
|
123
|
+
path Relative path from the cwd — local only.
|
|
124
|
+
fullPath Absolute path on your machine — local only.
|
|
125
|
+
tags YAML front-matter tags. Editable when the Bridge is
|
|
126
|
+
connected; read-only otherwise.
|
|
127
|
+
|
|
128
|
+
Local fields (path, fullPath) are passed to the browser via a
|
|
129
|
+
separate URL parameter that JS reads into memory and then strips
|
|
130
|
+
from the address bar on load. They never appear in any URL the
|
|
131
|
+
user can copy, and \`sdoc share <file>\` never includes them in
|
|
132
|
+
the generated link. If someone opens your shared URL, only
|
|
133
|
+
\`file\` is visible.
|
|
134
|
+
|
|
135
|
+
LIBRARY (personal, on-machine index)
|
|
136
|
+
Every \`sdoc <file>\` records the file in ~/.sdocs/library-index.json -
|
|
137
|
+
path, title, tags, last-touched. \`sdoc library\` opens a search UI at
|
|
138
|
+
smalldocs.org/library that talks to a local loopback agent the CLI runs
|
|
139
|
+
on 127.0.0.1:47843. Click a result to open the file for live editing.
|
|
140
|
+
Nothing about the library ever leaves the machine.
|
|
141
|
+
|
|
142
|
+
Top commands:
|
|
143
|
+
sdoc library Open the search UI.
|
|
144
|
+
sdoc library ls List indexed files in this project.
|
|
145
|
+
sdoc library ls --tags Tag bag (tag - count) for this project.
|
|
146
|
+
sdoc library rebuild Walk \$HOME and refresh every entry.
|
|
147
|
+
sdoc library status Show enabled/disabled + entry count.
|
|
148
|
+
sdoc library autostart Manage the macOS LaunchAgent.
|
|
149
|
+
|
|
150
|
+
Tagging works two ways, both ending up in the file's YAML front
|
|
151
|
+
matter on disk:
|
|
152
|
+
1. Front-matter \`tags: [a, b]\` written directly (or edited via
|
|
153
|
+
the Tags row in the file-info card when the Bridge is live).
|
|
154
|
+
2. CLI flag at open time: \`sdoc plan.md +planning +q2\` injects
|
|
155
|
+
those tags into the file's front matter. The \`+\` prefix is
|
|
156
|
+
shell-safe (\`#\` would be eaten as a comment).
|
|
157
|
+
|
|
158
|
+
Files in OS-managed temp folders (/tmp, ~/.cache, ~/.Trash, etc.)
|
|
159
|
+
get a RESCUE COPY at index time so the entry survives when the OS
|
|
160
|
+
cleans the original. Per-file opt-out: \`sdocs-library: false\` in
|
|
161
|
+
the file's front matter. Per-directory opt-out: drop a .sdocsignore
|
|
162
|
+
(subset of gitignore syntax) into any directory.
|
|
163
|
+
|
|
164
|
+
Run \`sdoc library --help\` for the full reference - commands,
|
|
165
|
+
tagging, rescue, autostart, security gates, and answers to common
|
|
166
|
+
questions ("I added a tag and it didn't save", "an entry seems
|
|
167
|
+
stale", "how do I keep this work folder out of the library").
|
|
168
|
+
|
|
169
|
+
SHORT LINKS (sdoc share --short)
|
|
170
|
+
By default, \`sdoc share <file>\` encodes the document into the URL hash:
|
|
171
|
+
\`https://smalldocs.org/#md=<base64url>\`. The whole document lives in the
|
|
172
|
+
hash, which the browser does not send to any server.
|
|
173
|
+
|
|
174
|
+
\`--short\` produces a shorter, encrypted form: \`https://smalldocs.org/s/<id>#k=<key>\`.
|
|
175
|
+
|
|
176
|
+
How it works:
|
|
177
|
+
1. The CLI brotli-compresses the content, generates a 256-bit AES-GCM
|
|
178
|
+
key + 96-bit nonce locally, and encrypts the compressed bytes.
|
|
179
|
+
2. The CLI POSTs the ciphertext (nonce + ct + auth tag, base64url) to
|
|
180
|
+
/api/short. The server stores it under a random short id and
|
|
181
|
+
returns the id. The key NEVER leaves the CLI.
|
|
182
|
+
3. The CLI assembles \`https://smalldocs.org/s/<id>#k=<key>\` and copies
|
|
183
|
+
it to the clipboard. The key lives in the URL fragment, which the
|
|
184
|
+
browser does not send to the server on page load.
|
|
185
|
+
4. Whoever opens the link: the browser fetches the ciphertext from
|
|
186
|
+
/api/short/<id>, reads \`#k=\` from window.location.hash, decrypts
|
|
187
|
+
in JavaScript, and renders.
|
|
188
|
+
|
|
189
|
+
What the server can see:
|
|
190
|
+
- That a ciphertext blob was uploaded under id <id>.
|
|
191
|
+
- When it was fetched and from which IP (standard server logs).
|
|
192
|
+
What the server cannot see:
|
|
193
|
+
- The plaintext document. It does not have the key.
|
|
194
|
+
- The key. The key never leaves the URL fragment.
|
|
195
|
+
|
|
196
|
+
Trade-offs vs the default \`#md=\` hash form:
|
|
197
|
+
- + Shorter URL.
|
|
198
|
+
- + Survives URL-length limits (some chat apps truncate at ~2k chars).
|
|
199
|
+
- - Requires the server to remain reachable (the default form does not).
|
|
200
|
+
- - Stored ciphertext can be deleted by the operator at any time.
|
|
201
|
+
- - Server logs reveal access patterns even though contents are encrypted.
|
|
202
|
+
|
|
203
|
+
If you don't trust an SmallDocs operator with even those metadata, use the
|
|
204
|
+
default \`sdoc share <file>\` (no --short) which never contacts the
|
|
205
|
+
server at all.
|
|
206
|
+
|
|
207
|
+
VERIFYING THE SERVER (sdoc safe)
|
|
208
|
+
\`sdoc safe\` asks https://smalldocs.org what commit it is running, pulls the
|
|
209
|
+
authoritative fingerprint list for that commit from GitHub (published by the
|
|
210
|
+
publish-manifest workflow on every push to main), downloads every frontend
|
|
211
|
+
file from the host, hashes each one with SHA-256, and compares. Bytes come
|
|
212
|
+
from the host; fingerprints come from GitHub. The host cannot produce a
|
|
213
|
+
match it did not already publish to GitHub.
|
|
214
|
+
|
|
215
|
+
It does not prove anything about server-side code (that runs on a machine
|
|
216
|
+
we control). \`sdoc safe --audit\` prints GitHub links to the server files
|
|
217
|
+
an agent or human would need to read to audit the rest.
|
|
218
|
+
|
|
219
|
+
\`sdoc safe --json\` returns structured output for scripting.
|
|
220
|
+
|
|
221
|
+
MATH
|
|
222
|
+
Inline $...$ and display $$...$$ are rendered as LaTeX via KaTeX.
|
|
223
|
+
Inline: The energy is $E = mc^2$.
|
|
224
|
+
Display: $$\\int_0^\\infty e^{-x^2}\\,dx = \\frac{\\sqrt{\\pi}}{2}$$
|
|
225
|
+
Supported commands: https://katex.org/docs/supported.html
|
|
226
|
+
|
|
227
|
+
STYLED MARKDOWN FORMAT
|
|
228
|
+
SmallDocs extends standard .md files with an optional YAML
|
|
229
|
+
front matter block (the same standard used by Jekyll, Hugo, Obsidian).
|
|
230
|
+
The \`styles\` key controls every visual aspect of the rendered document.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
title: "My Document"
|
|
234
|
+
styles:
|
|
235
|
+
fontFamily: Inter
|
|
236
|
+
baseFontSize: 16
|
|
237
|
+
color: "#1c1917"
|
|
238
|
+
h1: { fontSize: 2.2, color: "#1a3a5c", fontWeight: 700 }
|
|
239
|
+
p: { lineHeight: 1.85, marginBottom: 1.1 }
|
|
240
|
+
---
|
|
241
|
+
# My Document
|
|
242
|
+
Content here...
|
|
243
|
+
|
|
244
|
+
Colors work in both themes automatically — dark mode versions
|
|
245
|
+
are generated by inverting lightness. Use \`dark:\` to override.
|
|
246
|
+
|
|
247
|
+
COMMENTS
|
|
248
|
+
SmallDocs files can carry reviewer comments in their YAML front matter
|
|
249
|
+
under a \`comments:\` key. Comments do not modify the body — they're
|
|
250
|
+
resolved at render time by index lookup with a text-based fallback.
|
|
251
|
+
A typical use:
|
|
252
|
+
1. an agent generates a draft .md file
|
|
253
|
+
2. a human reads it via \`sdoc <file> --comment\`, leaves comments
|
|
254
|
+
3. the user copies the .md back to the agent (with comments)
|
|
255
|
+
4. the agent processes the comments and regenerates
|
|
256
|
+
|
|
257
|
+
Or the inverse: an agent writes comments into the front matter to
|
|
258
|
+
flag uncertainty, and runs \`sdoc <file> --comment\` to surface them
|
|
259
|
+
for the human.
|
|
260
|
+
|
|
261
|
+
Run \`sdoc comments\` for the full format reference and authoring guide.
|
|
262
|
+
Run \`sdoc schema\` for the complete list of style properties.
|
|
263
|
+
Run \`sdoc charts\` for chart types, options, and styling.
|
|
264
|
+
Run \`sdoc library --help\` for the personal markdown library reference.
|
|
265
|
+
`;
|
|
266
|
+
|
|
267
|
+
const COMMENTS_HELP = `
|
|
268
|
+
SmallDocs — Comments
|
|
269
|
+
================
|
|
270
|
+
Reviewer comments are stored in YAML front matter under \`comments:\`.
|
|
271
|
+
The body is never modified — anchoring happens at render time.
|
|
272
|
+
This makes the format safe for round-tripping through agents and
|
|
273
|
+
markdown tooling that doesn't understand SmallDocs-specific markers.
|
|
274
|
+
|
|
275
|
+
WHEN TO USE THIS
|
|
276
|
+
Two flows benefit from comments:
|
|
277
|
+
|
|
278
|
+
1. Human reviewing agent output. The agent generates a .md file,
|
|
279
|
+
the human runs \`sdoc <file> --comment\`, leaves notes, and pastes
|
|
280
|
+
the file (with its YAML) back to the agent. The agent reads
|
|
281
|
+
\`comments:\` and acts on each entry.
|
|
282
|
+
|
|
283
|
+
2. Agent flagging uncertainty for a human. The agent writes one or
|
|
284
|
+
more comments into the front matter, then opens the file with
|
|
285
|
+
\`sdoc <file> --comment\` so the user sees the annotations rendered
|
|
286
|
+
beside the relevant blocks.
|
|
287
|
+
|
|
288
|
+
OPENING IN COMMENT MODE
|
|
289
|
+
sdoc <file> --comment Open in comment mode (or --mode comment)
|
|
290
|
+
|
|
291
|
+
Comment mode shows a gutter "+" button beside every top-level block
|
|
292
|
+
for adding new comments, and renders existing comments as yellow
|
|
293
|
+
sidecar cards beneath their anchored blocks.
|
|
294
|
+
|
|
295
|
+
TWO INPUT FORMATS
|
|
296
|
+
SmallDocs accepts comments in two interchangeable formats. Both render
|
|
297
|
+
identically in comment mode. Pick whichever is more natural for the
|
|
298
|
+
context:
|
|
299
|
+
|
|
300
|
+
1. Markdown footnote format (RECOMMENDED FOR AGENTS).
|
|
301
|
+
Standard markdown footnote syntax. The agent edits the body,
|
|
302
|
+
adding [^cN] markers where the comment anchors. No counting of
|
|
303
|
+
element indices required — anchoring is positional, computed
|
|
304
|
+
from the marker's position in the body.
|
|
305
|
+
|
|
306
|
+
2. YAML front-matter format.
|
|
307
|
+
The canonical on-disk store. Used by the SmallDocs UI and round-trip
|
|
308
|
+
export. Comments live as a structured list under \`comments:\`.
|
|
309
|
+
|
|
310
|
+
At load time, SmallDocs parses both: footnote markers are lifted out of
|
|
311
|
+
the body and merged with the YAML list. On save (round-trip export),
|
|
312
|
+
comments are normalised to YAML.
|
|
313
|
+
|
|
314
|
+
AUTHORING VIA MARKDOWN FOOTNOTES
|
|
315
|
+
Recommended path for agents that produce text. No tag:n counting,
|
|
316
|
+
no block_text, just standard markdown. Two patterns:
|
|
317
|
+
|
|
318
|
+
Inline (anchor a specific phrase):
|
|
319
|
+
Wrap the phrase in [phrase][^cN] and add the definition at the
|
|
320
|
+
end of the document.
|
|
321
|
+
|
|
322
|
+
The migration was [implemented in three weeks][^c1] this quarter.
|
|
323
|
+
|
|
324
|
+
[^c1]: agent - actually slipped to five weeks
|
|
325
|
+
|
|
326
|
+
Block (anchor an entire paragraph or heading):
|
|
327
|
+
Place a lone [^cN] at the end of the block (after the closing
|
|
328
|
+
period) and add the definition at the end.
|
|
329
|
+
|
|
330
|
+
The reliability picture was equally encouraging.[^c2]
|
|
331
|
+
|
|
332
|
+
[^c2]: agent - need to specify what "incident-free" means
|
|
333
|
+
|
|
334
|
+
Definitions support optional author and a [resolved] marker:
|
|
335
|
+
[^c3]: priya [resolved] - already addressed
|
|
336
|
+
[^c4]: agent - check Q2 numbers (block p:5)
|
|
337
|
+
|
|
338
|
+
Only footnote ids matching the cN pattern (c1, c2, ...) are treated
|
|
339
|
+
as comments. Other footnote ids (e.g. [^citation1]) keep standard
|
|
340
|
+
footnote semantics.
|
|
341
|
+
|
|
342
|
+
This format renders sensibly in any markdown viewer — refs as
|
|
343
|
+
superscripts, definitions at the bottom — so the file is useful
|
|
344
|
+
outside SmallDocs too.
|
|
345
|
+
|
|
346
|
+
COMMENT KINDS
|
|
347
|
+
block Anchored to an entire block element (paragraph, heading,
|
|
348
|
+
list, code block, table, blockquote).
|
|
349
|
+
inline Anchored to a specific text span within a block.
|
|
350
|
+
|
|
351
|
+
THE BLOCK ID SCHEME
|
|
352
|
+
Both kinds carry a \`block\` field of the form "tag:n":
|
|
353
|
+
- tag is the lowercased HTML element name (p, h1, h2, h3, h4,
|
|
354
|
+
ul, ol, pre, blockquote, table, plus "chart" for chart blocks).
|
|
355
|
+
- n is the 0-indexed position of that element among siblings of
|
|
356
|
+
the same tag, in render order across the entire document.
|
|
357
|
+
|
|
358
|
+
Examples:
|
|
359
|
+
"h2:0" First <h2> in the document.
|
|
360
|
+
"p:3" Fourth <p> in render order (ignores headings/lists).
|
|
361
|
+
"ul:0" First unordered list.
|
|
362
|
+
"pre:1" Second code block.
|
|
363
|
+
|
|
364
|
+
Per-tag-type indexing is more resilient to reordering than a single
|
|
365
|
+
global ordinal, but indices still drift if blocks of the same type
|
|
366
|
+
are inserted upstream. See "Survival hints" below.
|
|
367
|
+
|
|
368
|
+
SCHEMA — A FULLY-POPULATED EXAMPLE
|
|
369
|
+
---
|
|
370
|
+
title: "Q2 Roadmap (Draft)"
|
|
371
|
+
# Comments: block "tag:n" = nth (0-indexed) <tag> in render order.
|
|
372
|
+
# block kind may carry block_text (first ~60 chars) as a survival hint when the index drifts.
|
|
373
|
+
# inline kind anchors via quote (+ optional prefix/suffix). resolved: true marks addressed.
|
|
374
|
+
comments:
|
|
375
|
+
- id: c1
|
|
376
|
+
kind: block
|
|
377
|
+
block: "h2:0"
|
|
378
|
+
block_text: "Context"
|
|
379
|
+
author: priya
|
|
380
|
+
color: "#ffbb00"
|
|
381
|
+
at: "2026-04-22T09:14:00Z"
|
|
382
|
+
text: "rename this to 'Where Q1 left us' — sharper"
|
|
383
|
+
- id: c2
|
|
384
|
+
kind: inline
|
|
385
|
+
quote: "shipped on time"
|
|
386
|
+
prefix: "every committed feature "
|
|
387
|
+
suffix: " and within budget"
|
|
388
|
+
block: "p:0"
|
|
389
|
+
author: priya
|
|
390
|
+
color: "#ffbb00"
|
|
391
|
+
at: "2026-04-22T09:15:00Z"
|
|
392
|
+
text: "auth migration slipped 2 weeks — please correct"
|
|
393
|
+
- id: c3
|
|
394
|
+
kind: block
|
|
395
|
+
block: "p:5"
|
|
396
|
+
block_text: "Cost discipline becomes more visible in Q2"
|
|
397
|
+
author: priya
|
|
398
|
+
color: "#ffbb00"
|
|
399
|
+
at: "2026-04-22T09:24:00Z"
|
|
400
|
+
text: "align the $180k figure with finance before publishing"
|
|
401
|
+
resolved: true
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
# Q2 Roadmap (Draft)
|
|
405
|
+
## Context
|
|
406
|
+
Q1 closed strong: every committed feature shipped on time and within budget...
|
|
407
|
+
|
|
408
|
+
FIELDS
|
|
409
|
+
Required for both kinds:
|
|
410
|
+
id Stable identifier. Convention: c1, c2, c3...
|
|
411
|
+
kind "block" or "inline"
|
|
412
|
+
text The reviewer's note (the comment body).
|
|
413
|
+
|
|
414
|
+
Required for inline:
|
|
415
|
+
quote The exact text span in the rendered body to highlight.
|
|
416
|
+
|
|
417
|
+
Optional but recommended:
|
|
418
|
+
block The "tag:n" anchor. Used as a fast lookup. Optional
|
|
419
|
+
for inline (the quote alone is enough), required for
|
|
420
|
+
block (it's the only anchor).
|
|
421
|
+
block_text For block kind only. The first ~60 characters of
|
|
422
|
+
the block's plain text at the time of writing.
|
|
423
|
+
Survival hint: when "tag:n" no longer matches (the
|
|
424
|
+
document was edited and indices drifted), readers
|
|
425
|
+
fall back to scanning for a block whose start
|
|
426
|
+
matches block_text.
|
|
427
|
+
prefix For inline kind. Up to 60 chars of the rendered
|
|
428
|
+
text immediately before the quote, used to
|
|
429
|
+
disambiguate when the quote appears multiple times.
|
|
430
|
+
suffix Same as prefix but for the text immediately after.
|
|
431
|
+
resolved true if the comment has been addressed. Preserved
|
|
432
|
+
for audit; readers should skip resolved comments
|
|
433
|
+
when generating action lists.
|
|
434
|
+
author Display name on the rendered card. Default: "user".
|
|
435
|
+
color Card tint, hex (#rrggbb). Default: "#ffbb00" (yellow).
|
|
436
|
+
at ISO 8601 timestamp. Default: now (browser side).
|
|
437
|
+
|
|
438
|
+
ID GENERATION
|
|
439
|
+
Use c1, c2, c3... in chronological order. To pick the next id, take
|
|
440
|
+
the highest cN currently in the file and add 1. Don't reuse ids of
|
|
441
|
+
deleted comments — gaps are fine. Non-cN ids are tolerated but lose
|
|
442
|
+
the auto-increment guarantee.
|
|
443
|
+
|
|
444
|
+
ANCHOR RESOLUTION (HOW READERS RECOVER FROM DRIFT)
|
|
445
|
+
When a tool (the SmallDocs renderer or another agent) loads the file,
|
|
446
|
+
each comment is resolved in this order:
|
|
447
|
+
|
|
448
|
+
Block kind:
|
|
449
|
+
1. Try \`block: "tag:n"\` exactly.
|
|
450
|
+
2. If found, optionally verify the resolved block's leading text
|
|
451
|
+
matches \`block_text\`. If not, fall through.
|
|
452
|
+
3. Search the document for any block whose first ~60 chars start
|
|
453
|
+
with \`block_text\`.
|
|
454
|
+
4. Give up — comment is orphaned.
|
|
455
|
+
|
|
456
|
+
Inline kind:
|
|
457
|
+
1. Find the block via \`block: "tag:n"\`.
|
|
458
|
+
2. Inside that block, find \`prefix + quote + suffix\`.
|
|
459
|
+
3. Fall back to \`prefix + quote + suffix\` anywhere in the body.
|
|
460
|
+
4. Fall back to \`quote\` alone, anywhere in the body.
|
|
461
|
+
5. Give up — comment is orphaned.
|
|
462
|
+
|
|
463
|
+
AUTHORING TIPS FOR AGENTS
|
|
464
|
+
- Prefer the markdown-footnote authoring path (above). It avoids
|
|
465
|
+
the index-counting work the YAML path requires and is the most
|
|
466
|
+
reliable way for an LLM to write a comment that anchors correctly.
|
|
467
|
+
- If you do author in YAML directly:
|
|
468
|
+
- Compute "tag:n" by counting same-tag elements in render order.
|
|
469
|
+
Headings, paragraphs, lists each have their own counters.
|
|
470
|
+
- Counting errors are common. The fallback tiers (block_text
|
|
471
|
+
for block kind, prefix/suffix or quote-only search for inline)
|
|
472
|
+
will rescue an off-by-one index — but only if you populate them.
|
|
473
|
+
- For block comments, ALWAYS populate block_text (first ~60 chars
|
|
474
|
+
of the block's plain text).
|
|
475
|
+
- For inline comments, ensure the comment is uniquely resolvable:
|
|
476
|
+
either pick a long unique quote, or populate prefix/suffix.
|
|
477
|
+
- To mark a comment addressed without losing audit trail, set
|
|
478
|
+
\`resolved: true\` (YAML) or add \`[resolved]\` after the author
|
|
479
|
+
name in the footnote definition.
|
|
480
|
+
- When acting on comments, skip those marked resolved — they
|
|
481
|
+
describe past work, not pending requests.
|
|
482
|
+
`;
|
|
483
|
+
|
|
484
|
+
const SCHEMA = `
|
|
485
|
+
SmallDocs — Styles Schema
|
|
486
|
+
=====================
|
|
487
|
+
All style values live under the \`styles:\` key in YAML front matter.
|
|
488
|
+
Every property is optional — omit anything you want left at its default.
|
|
489
|
+
|
|
490
|
+
┌────────────────────────────────────────────────────────────┐
|
|
491
|
+
│ IMPORTANT — if you set ANY custom colours, run: │
|
|
492
|
+
│ sdoc color-analysis <file> │
|
|
493
|
+
│ It grades every text-on-background pair for BOTH the light │
|
|
494
|
+
│ and dark themes and flags anything unreadable. Hand-picked │
|
|
495
|
+
│ palettes (especially when you only looked at one theme) are │
|
|
496
|
+
│ the #1 source of unreadable docs. Run it before you ship. │
|
|
497
|
+
└────────────────────────────────────────────────────────────┘
|
|
498
|
+
|
|
499
|
+
GENERAL
|
|
500
|
+
fontFamily string Any of the supported fonts (see FONTS below)
|
|
501
|
+
Default: "Inter"
|
|
502
|
+
baseFontSize number Base font size in px. All rem/em values scale from this.
|
|
503
|
+
Default: 16
|
|
504
|
+
background string Page background color (hex).
|
|
505
|
+
Default: "#ffffff" (light) / "#2c2a26" (dark)
|
|
506
|
+
color string Master body text color (hex). Cascades to headings,
|
|
507
|
+
paragraphs, and lists unless those are overridden.
|
|
508
|
+
Default: "#1c1917"
|
|
509
|
+
lineHeight number Global line-height multiplier.
|
|
510
|
+
Default: 1.75
|
|
511
|
+
|
|
512
|
+
HEADINGS (general heading controls)
|
|
513
|
+
headers:
|
|
514
|
+
scale number Relative size multiplier applied across all heading levels.
|
|
515
|
+
Default: 1.0
|
|
516
|
+
marginBottom number Space below headings (em). Default: 0.4
|
|
517
|
+
color string Heading color — cascades to h1/h2/h3/h4 unless overridden.
|
|
518
|
+
Default: inherits \`color\`
|
|
519
|
+
|
|
520
|
+
PER-HEADING (each independently overrides the heading defaults above)
|
|
521
|
+
h1: { fontSize: number, color: string, fontWeight: number }
|
|
522
|
+
h2: { fontSize: number, color: string, fontWeight: number }
|
|
523
|
+
h3: { fontSize: number, color: string, fontWeight: number }
|
|
524
|
+
h4: { fontSize: number, color: string, fontWeight: number }
|
|
525
|
+
|
|
526
|
+
fontSize is in rem (relative to baseFontSize).
|
|
527
|
+
Sensible defaults: h1 2.2, h2 1.55, h3 1.2, h4 1.0
|
|
528
|
+
fontWeight: 400 (regular) · 600 (semibold) · 700 (bold)
|
|
529
|
+
|
|
530
|
+
PARAGRAPH
|
|
531
|
+
p:
|
|
532
|
+
lineHeight number Line height for body paragraphs. Default: 1.75
|
|
533
|
+
marginBottom number Space between paragraphs (em). Default: 1.1
|
|
534
|
+
color string Paragraph text color. Default: inherits \`color\`
|
|
535
|
+
|
|
536
|
+
LISTS
|
|
537
|
+
list:
|
|
538
|
+
color string Color for list items and bullet/number markers.
|
|
539
|
+
Default: inherits paragraph color
|
|
540
|
+
|
|
541
|
+
LINKS
|
|
542
|
+
link:
|
|
543
|
+
color string Link color. Default: "#2563eb"
|
|
544
|
+
decoration string "underline" | "none". Default: "underline"
|
|
545
|
+
|
|
546
|
+
CODE
|
|
547
|
+
code:
|
|
548
|
+
fontFamily string Monospace font. Default: "ui-monospace, monospace"
|
|
549
|
+
background string Inline/block code background color. Default: "#F1EDE8"
|
|
550
|
+
padding number Inline code padding (em). Default: 0.2
|
|
551
|
+
|
|
552
|
+
BLOCKQUOTE
|
|
553
|
+
blockquote:
|
|
554
|
+
borderColor string Left border accent color. Default: "#2563eb"
|
|
555
|
+
borderWidth number Left border thickness (px). Default: 3
|
|
556
|
+
background string Quote background color. Default: "#f7f5f2"
|
|
557
|
+
color string Quote text color. Default: "#6b6560"
|
|
558
|
+
|
|
559
|
+
BLOCKS (shared styling for code, blockquote, and chart blocks)
|
|
560
|
+
blocks:
|
|
561
|
+
background string Background for all block types. Cascades to code,
|
|
562
|
+
blockquote, and chart backgrounds unless overridden.
|
|
563
|
+
color string Text color for all block types. Cascades to code,
|
|
564
|
+
blockquote, and chart text unless overridden.
|
|
565
|
+
|
|
566
|
+
CHARTS
|
|
567
|
+
chart:
|
|
568
|
+
accent string Palette base color (hex). Default: "#3b82f6"
|
|
569
|
+
palette string Palette mode. Default: "monochrome"
|
|
570
|
+
Options: monochrome, complementary, analogous, triadic,
|
|
571
|
+
pastel, warm, cool, earth
|
|
572
|
+
background string Chart background. Default: inherits blocks.background
|
|
573
|
+
textColor string Chart labels/axes. Default: inherits blocks.color
|
|
574
|
+
|
|
575
|
+
Run \`sdoc charts\` for the full chart reference — chart types, JSON
|
|
576
|
+
format, axis/legend/annotation options, and per-chart styling overrides.
|
|
577
|
+
|
|
578
|
+
TABLES
|
|
579
|
+
table:
|
|
580
|
+
color string Text color for header and body cells.
|
|
581
|
+
Default: inherits \`color\`
|
|
582
|
+
headerBackground string Background for the header row.
|
|
583
|
+
Default: "#f4f1ed" (light) / "#2c2926" (dark)
|
|
584
|
+
oddBackground string Background for odd body rows.
|
|
585
|
+
Default: "#ffffff" (light) / "#2c2a26" (dark)
|
|
586
|
+
evenBackground string Background for even body rows.
|
|
587
|
+
Default: "#fafaf8" (light) / "#242220" (dark)
|
|
588
|
+
border string Cell border color.
|
|
589
|
+
Default: "#e2ddd6" (light) / "#3d3935" (dark)
|
|
590
|
+
|
|
591
|
+
SLIDES
|
|
592
|
+
Slides embedded via \`\`\`slide / ~~~slide fenced blocks inherit these
|
|
593
|
+
styles automatically (background, fonts, link color, code block colors,
|
|
594
|
+
heading colors on shape markdown). For explicit references, shape
|
|
595
|
+
attributes accept \$path.to.prop tokens - e.g. \`fill=\$h1.color\`,
|
|
596
|
+
\`bg=\$blocks.background\`. You should now run \`sdoc slides\` for the
|
|
597
|
+
full slide reference, so you don't guess at the DSL.
|
|
598
|
+
|
|
599
|
+
COLOR CASCADE
|
|
600
|
+
Colors cascade from general → specific:
|
|
601
|
+
color → headers.color → h1.color, h2.color, h3.color, h4.color
|
|
602
|
+
color → p.color → list.color
|
|
603
|
+
blocks.background → code.background, blockquote.background, chart.background
|
|
604
|
+
blocks.color → code.color, blockquote.color, chart.textColor
|
|
605
|
+
Set a child color only when you want it to differ from its parent.
|
|
606
|
+
|
|
607
|
+
THEME COLORS
|
|
608
|
+
Top-level colors are light-mode colors. Dark mode is auto-generated
|
|
609
|
+
by inverting lightness (same hue, flipped brightness). Light backgrounds
|
|
610
|
+
become dark, dark text becomes light. Colors already very dark (like a
|
|
611
|
+
dark code block background) are kept as-is.
|
|
612
|
+
|
|
613
|
+
This means you only need to specify colors ONCE:
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
styles:
|
|
617
|
+
color: "#2d1810"
|
|
618
|
+
background: "#fdf6f0"
|
|
619
|
+
headers: { color: "#8b2500" }
|
|
620
|
+
blocks:
|
|
621
|
+
background: "#f5e6d8"
|
|
622
|
+
color: "#5a3e2e"
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
Dark mode will automatically get inverted versions of all colors above.
|
|
626
|
+
|
|
627
|
+
To override specific dark-mode colors, add a \`dark:\` block:
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
styles:
|
|
631
|
+
color: "#2d1810"
|
|
632
|
+
background: "#fdf6f0"
|
|
633
|
+
blocks:
|
|
634
|
+
background: "#f5e6d8"
|
|
635
|
+
dark:
|
|
636
|
+
background: "#1a1210"
|
|
637
|
+
blocks:
|
|
638
|
+
background: "#2a1a1a"
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
Non-color properties (fonts, sizes, spacing, weights) remain at the
|
|
642
|
+
top level and are shared across both themes.
|
|
643
|
+
|
|
644
|
+
Because dark mode is auto-derived, a colour that reads fine in the
|
|
645
|
+
light theme can invert to something unreadable in the dark theme (a
|
|
646
|
+
mid-blue heading becomes dark navy, light text becomes near-black).
|
|
647
|
+
ALWAYS run \`sdoc color-analysis <file>\` after setting colours — it
|
|
648
|
+
checks both themes and tells you exactly which pair fails and why.
|
|
649
|
+
|
|
650
|
+
FONTS (24 supported, loaded lazily from Google Fonts)
|
|
651
|
+
Inter · Roboto · Open Sans · Lato · Montserrat · Source Sans 3
|
|
652
|
+
Oswald · Raleway · Poppins · Merriweather · Ubuntu · Nunito
|
|
653
|
+
Playfair Display · Roboto Slab · PT Sans · Lora · Mulish · Noto Sans
|
|
654
|
+
Rubik · Dosis · Josefin Sans · PT Serif · Libre Franklin · Crimson Text
|
|
655
|
+
|
|
656
|
+
EXAMPLE — editorial article with colored heading tiers
|
|
657
|
+
---
|
|
658
|
+
styles:
|
|
659
|
+
fontFamily: Lora
|
|
660
|
+
baseFontSize: 17
|
|
661
|
+
background: "#fffaf5"
|
|
662
|
+
color: "#1a1a2e"
|
|
663
|
+
h1: { fontSize: 2.3, fontWeight: 700, color: "#c0392b" }
|
|
664
|
+
h2: { fontSize: 1.55, fontWeight: 600, color: "#8e44ad" }
|
|
665
|
+
h3: { fontSize: 1.2, fontWeight: 600, color: "#16a085" }
|
|
666
|
+
p: { lineHeight: 1.9, marginBottom: 1.2 }
|
|
667
|
+
link: { color: "#e67e22" }
|
|
668
|
+
blocks:
|
|
669
|
+
background: "#faf0eb"
|
|
670
|
+
blockquote: { borderColor: "#c0392b", color: "#7f8c8d" }
|
|
671
|
+
dark:
|
|
672
|
+
background: "#1a1520"
|
|
673
|
+
h1: { color: "#ef6f5e" }
|
|
674
|
+
h2: { color: "#c490e4" }
|
|
675
|
+
blockquote: { borderColor: "#ef6f5e" }
|
|
676
|
+
---
|
|
677
|
+
`;
|
|
678
|
+
|
|
679
|
+
const CHARTS_HELP = `
|
|
680
|
+
SmallDocs — Charts
|
|
681
|
+
==============
|
|
682
|
+
Render beautiful charts in markdown using \`\`\`chart code blocks.
|
|
683
|
+
Charts are powered by Chart.js, loaded lazily from CDN only when needed.
|
|
684
|
+
|
|
685
|
+
BASIC SYNTAX
|
|
686
|
+
Wrap a JSON object in a \`\`\`chart fenced code block:
|
|
687
|
+
|
|
688
|
+
\`\`\`chart
|
|
689
|
+
{
|
|
690
|
+
"type": "bar",
|
|
691
|
+
"title": "Monthly Revenue",
|
|
692
|
+
"labels": ["Jan", "Feb", "Mar"],
|
|
693
|
+
"values": [100, 150, 130]
|
|
694
|
+
}
|
|
695
|
+
\`\`\`
|
|
696
|
+
|
|
697
|
+
CHART TYPES
|
|
698
|
+
pie Circular segments (use "color" for monochrome shading)
|
|
699
|
+
doughnut Hollow-center pie (alias: donut)
|
|
700
|
+
bar Vertical bars
|
|
701
|
+
horizontal_bar Horizontal bars (alias: hbar)
|
|
702
|
+
stacked_bar Stacked vertical bars
|
|
703
|
+
line Line graph with data points
|
|
704
|
+
area Line with filled area beneath
|
|
705
|
+
stacked_area Multiple filled areas stacked (alias: stacked_line)
|
|
706
|
+
radar Spider/web chart for multi-axis comparison
|
|
707
|
+
polarArea Like pie but equal angles, varying radius
|
|
708
|
+
scatter X/Y point plots
|
|
709
|
+
bubble Like scatter with size dimension
|
|
710
|
+
mixed Combo chart — bar + line on same plot (alias: combo)
|
|
711
|
+
|
|
712
|
+
DATA FORMATS
|
|
713
|
+
Simple (single dataset):
|
|
714
|
+
"labels": ["A", "B", "C"],
|
|
715
|
+
"values": [10, 20, 15]
|
|
716
|
+
|
|
717
|
+
Multi-dataset:
|
|
718
|
+
"labels": ["Q1", "Q2"],
|
|
719
|
+
"datasets": [
|
|
720
|
+
{ "label": "2024", "values": [10, 20] },
|
|
721
|
+
{ "label": "2025", "values": [12, 25] }
|
|
722
|
+
]
|
|
723
|
+
|
|
724
|
+
Scatter/Bubble:
|
|
725
|
+
"datasets": [
|
|
726
|
+
{ "label": "Group", "data": [{"x": 1, "y": 2}, {"x": 3, "y": 5}] }
|
|
727
|
+
]
|
|
728
|
+
|
|
729
|
+
CHART OPTIONS
|
|
730
|
+
title string Chart heading
|
|
731
|
+
subtitle string Smaller text below title
|
|
732
|
+
labels string[] Category labels
|
|
733
|
+
values number[] Data for a single dataset
|
|
734
|
+
datasets array Multiple datasets (see above)
|
|
735
|
+
color string Single accent color (hex)
|
|
736
|
+
colors string[] Per-segment/bar custom colors
|
|
737
|
+
|
|
738
|
+
AXIS OPTIONS
|
|
739
|
+
xAxis / xLabel string X-axis label
|
|
740
|
+
yAxis / yLabel string Y-axis label
|
|
741
|
+
y2Axis string Right y-axis label (enables dual axis)
|
|
742
|
+
min number Minimum value on value axis
|
|
743
|
+
max number Maximum value on value axis
|
|
744
|
+
stepSize number Tick interval
|
|
745
|
+
beginAtZero boolean Default true. Set false for auto-range.
|
|
746
|
+
|
|
747
|
+
NUMBER FORMATTING
|
|
748
|
+
format string "currency" ($), "euro" (€), "pound" (£),
|
|
749
|
+
"percent" (%), "comma" (1,000)
|
|
750
|
+
prefix string Custom value prefix (e.g. "£")
|
|
751
|
+
suffix string Custom value suffix (e.g. " kg", "°C")
|
|
752
|
+
y2Format string Format for right y-axis
|
|
753
|
+
y2Prefix string Prefix for right y-axis
|
|
754
|
+
y2Suffix string Suffix for right y-axis
|
|
755
|
+
|
|
756
|
+
DISPLAY OPTIONS
|
|
757
|
+
legend boolean Show/hide legend (auto by default)
|
|
758
|
+
legendPosition string "top", "bottom" (default), "left", "right"
|
|
759
|
+
dataLabels boolean Show values on chart (default true). Set false for clean look.
|
|
760
|
+
aspectRatio number Width/height ratio (e.g. 2 for wide, 0.8 for tall)
|
|
761
|
+
stacked boolean Force stacking on bar/line charts
|
|
762
|
+
|
|
763
|
+
DATASET OPTIONS (inside each dataset object)
|
|
764
|
+
label string Name shown in legend
|
|
765
|
+
values number[] Data points
|
|
766
|
+
data object[] For scatter: [{x, y}], for bubble: [{x, y, r}]
|
|
767
|
+
color string Dataset color (hex)
|
|
768
|
+
colors string[] Per-bar colors within dataset
|
|
769
|
+
type string Override type in mixed charts ("bar" or "line")
|
|
770
|
+
yAxisID string "y" (left) or "y2" (right) for dual-axis charts
|
|
771
|
+
fill boolean Fill area under line
|
|
772
|
+
tension number Line smoothing (0 = straight, 0.4 = smooth)
|
|
773
|
+
order number Draw order (lower = rendered on top)
|
|
774
|
+
|
|
775
|
+
ANNOTATIONS (reference lines)
|
|
776
|
+
"annotations": [
|
|
777
|
+
{ "y": 60, "label": "Target", "color": "#ef4444" },
|
|
778
|
+
{ "x": "Mar", "label": "Launch", "dashed": true }
|
|
779
|
+
]
|
|
780
|
+
|
|
781
|
+
y / x number/string Position of the reference line
|
|
782
|
+
label string Text label on the line
|
|
783
|
+
color string Line color
|
|
784
|
+
width number Line thickness (default 2)
|
|
785
|
+
dashed boolean Dashed style (default true)
|
|
786
|
+
position string Label position: "start", "center", "end"
|
|
787
|
+
|
|
788
|
+
CHART STYLING (via front matter or style panel)
|
|
789
|
+
Charts inherit background and text colors from the block cascade:
|
|
790
|
+
|
|
791
|
+
---
|
|
792
|
+
styles:
|
|
793
|
+
blocks:
|
|
794
|
+
background: "#1a1a2e" # all blocks: code, blockquote, charts
|
|
795
|
+
color: "#c8c3bc" # text in all blocks
|
|
796
|
+
chart:
|
|
797
|
+
accent: "#6366f1" # palette base color
|
|
798
|
+
palette: monochrome # palette generation mode
|
|
799
|
+
background: "#0e4a1a" # override blocks.background for charts only
|
|
800
|
+
textColor: "#c8f0d8" # override blocks.color for charts only
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
COLOR CASCADE FOR BLOCKS
|
|
804
|
+
blocks.background → code.background, blockquote.background, chart.background
|
|
805
|
+
blocks.color → code.color, blockquote.color, chart.textColor
|
|
806
|
+
Set a child value only when you want it to differ from the parent.
|
|
807
|
+
|
|
808
|
+
DARK MODE
|
|
809
|
+
All colors auto-generate dark-mode counterparts (lightness inverted).
|
|
810
|
+
Add a \`dark:\` block to override specific values:
|
|
811
|
+
dark:
|
|
812
|
+
blocks:
|
|
813
|
+
background: "#2a1a1a"
|
|
814
|
+
|
|
815
|
+
PALETTE MODES
|
|
816
|
+
monochrome Same hue, varying lightness (default)
|
|
817
|
+
complementary Hues spread evenly around the color wheel
|
|
818
|
+
analogous Neighboring hues for a harmonious feel
|
|
819
|
+
triadic Three base hues 120° apart
|
|
820
|
+
pastel Soft, light colors
|
|
821
|
+
warm Reds, oranges, yellows
|
|
822
|
+
cool Blues, teals, purples
|
|
823
|
+
earth Browns, olives, muted greens
|
|
824
|
+
|
|
825
|
+
Per-chart override: set "accent" and/or "palette" directly in the chart JSON.
|
|
826
|
+
Per-chart colors: set "colors": ["#hex", ...] to override the palette entirely.
|
|
827
|
+
Single-color pie: set "color": "#hex" on a pie/doughnut for monochrome shading.
|
|
828
|
+
|
|
829
|
+
MIXED CHART EXAMPLE (dual y-axis)
|
|
830
|
+
\`\`\`chart
|
|
831
|
+
{
|
|
832
|
+
"type": "mixed",
|
|
833
|
+
"title": "Revenue vs Growth",
|
|
834
|
+
"labels": ["Q1", "Q2", "Q3", "Q4"],
|
|
835
|
+
"datasets": [
|
|
836
|
+
{ "label": "Revenue", "type": "bar", "values": [50, 65, 80, 95], "yAxisID": "y" },
|
|
837
|
+
{ "label": "Growth", "type": "line", "values": [12, 30, 23, 19], "yAxisID": "y2" }
|
|
838
|
+
],
|
|
839
|
+
"yAxis": "Revenue ($M)",
|
|
840
|
+
"y2Axis": "Growth %",
|
|
841
|
+
"format": "currency",
|
|
842
|
+
"y2Format": "percent"
|
|
843
|
+
}
|
|
844
|
+
\`\`\`
|
|
845
|
+
`;
|
|
846
|
+
|
|
847
|
+
const DIAGRAMS_HELP = `
|
|
848
|
+
SmallDocs — Diagrams
|
|
849
|
+
================
|
|
850
|
+
Render Mermaid diagrams in markdown using \`\`\`mermaid code blocks.
|
|
851
|
+
Mermaid is loaded lazily from CDN only when a diagram is present.
|
|
852
|
+
|
|
853
|
+
BASIC SYNTAX
|
|
854
|
+
\`\`\`mermaid
|
|
855
|
+
graph TD
|
|
856
|
+
A[Start] --> B{Decision}
|
|
857
|
+
B -- yes --> C[Do this]
|
|
858
|
+
B -- no --> D[Do that]
|
|
859
|
+
\`\`\`
|
|
860
|
+
|
|
861
|
+
STANDALONE .mmd FILES
|
|
862
|
+
\`sdoc graph.mmd\` works like \`sdoc file.md\` - the CLI wraps the
|
|
863
|
+
contents in a \`\`\`mermaid fence before opening. Same for share:
|
|
864
|
+
\`sdoc share graph.mmd\`. \`.mermaid\` files work the same way.
|
|
865
|
+
|
|
866
|
+
SUPPORTED DIAGRAM TYPES
|
|
867
|
+
flowchart / graph flowchart TD, LR, etc.
|
|
868
|
+
sequenceDiagram interaction sequences
|
|
869
|
+
classDiagram UML-style class relationships
|
|
870
|
+
stateDiagram-v2 state machines
|
|
871
|
+
erDiagram entity-relationship
|
|
872
|
+
gantt timelines
|
|
873
|
+
pie proportional breakdown
|
|
874
|
+
journey user-journey diagrams
|
|
875
|
+
gitGraph git history visualisation
|
|
876
|
+
mindmap mind maps
|
|
877
|
+
timeline chronological events
|
|
878
|
+
quadrantChart 2x2 matrix
|
|
879
|
+
sankey-beta flow diagrams
|
|
880
|
+
See https://mermaid.js.org for the full syntax reference.
|
|
881
|
+
|
|
882
|
+
THEMING
|
|
883
|
+
Diagrams inherit colors from the SmallDocs blocks cascade:
|
|
884
|
+
|
|
885
|
+
\`\`\`yaml
|
|
886
|
+
styles:
|
|
887
|
+
blocks:
|
|
888
|
+
background: "#f4f1ed" # diagram wrapper bg
|
|
889
|
+
color: "#6b6560" # node text / lines
|
|
890
|
+
\`\`\`
|
|
891
|
+
|
|
892
|
+
In dark mode the inverted block colors apply automatically.
|
|
893
|
+
For finer-grained control, set Mermaid theme variables in the
|
|
894
|
+
diagram source itself, but note that \`%%{init:...}%%\` directives
|
|
895
|
+
are stripped by SmallDocs as a security measure (they can otherwise
|
|
896
|
+
override sanitisation settings at parse time).
|
|
897
|
+
|
|
898
|
+
LIMITS
|
|
899
|
+
- Per-diagram source cap: 64 KB.
|
|
900
|
+
- Per-document diagram cap: 50 (excess rendered as plain code).
|
|
901
|
+
- Per-render timeout: 5 seconds (large or pathological graphs error out).
|
|
902
|
+
|
|
903
|
+
SECURITY
|
|
904
|
+
Mermaid runs with \`securityLevel: 'strict'\` and \`htmlLabels: true\`.
|
|
905
|
+
htmlLabels lets long node labels wrap inside a \`<foreignObject>\`,
|
|
906
|
+
which is otherwise a script-injection vector; SmallDocs makes that safe
|
|
907
|
+
by post-sanitising the SVG before render. \`<script>\`, \`<iframe>\`,
|
|
908
|
+
\`<form>\`, \`<input>\`, \`<use>\`, animation tags, \`on*\` event handlers
|
|
909
|
+
and \`javascript:\` URLs are stripped (inside foreignObject and out).
|
|
910
|
+
Source caps and a render timeout cover the DoS surface. Treat diagram
|
|
911
|
+
source as untrusted - it travels in the URL hash with the rest of
|
|
912
|
+
the document.
|
|
913
|
+
|
|
914
|
+
EXAMPLE
|
|
915
|
+
\`\`\`mermaid
|
|
916
|
+
sequenceDiagram
|
|
917
|
+
participant U as User
|
|
918
|
+
participant S as SmallDocs
|
|
919
|
+
participant C as CDN
|
|
920
|
+
U->>S: open page with diagram
|
|
921
|
+
S->>C: load mermaid.min.js (lazy, first time only)
|
|
922
|
+
C-->>S: script
|
|
923
|
+
S->>S: render() → SVG
|
|
924
|
+
S->>U: paint diagram
|
|
925
|
+
\`\`\`
|
|
926
|
+
`;
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
const CELLS_HELP = `
|
|
930
|
+
SmallDocs - Cells (sheets)
|
|
931
|
+
======================
|
|
932
|
+
Render data as an inline spreadsheet using \`\`\`cells fenced code blocks.
|
|
933
|
+
The body is CSV; the grid shows column letters, row numbers, type-aware
|
|
934
|
+
alignment, click-and-drag selection, and a small copy toolbar. No CDN and
|
|
935
|
+
no dependency - it renders from the markdown itself.
|
|
936
|
+
|
|
937
|
+
BASIC SYNTAX
|
|
938
|
+
Put CSV inside a \`\`\`cells fence:
|
|
939
|
+
|
|
940
|
+
\`\`\`cells
|
|
941
|
+
Region,Q1,Q2,Q3
|
|
942
|
+
North,100,150,130
|
|
943
|
+
South,90,95,-7
|
|
944
|
+
\`\`\`
|
|
945
|
+
|
|
946
|
+
Every row is a row (numbered from 1); every column gets a letter
|
|
947
|
+
(A, B, C ...). A text first row over numeric data is auto-detected as a
|
|
948
|
+
header: shown bold and kept pinned to the top when you sort.
|
|
949
|
+
|
|
950
|
+
DATA & TYPES
|
|
951
|
+
Numbers are detected and align right; text aligns left; blank fields are
|
|
952
|
+
empty cells. Negatives and decimals are numbers. A quoted value like
|
|
953
|
+
"1,200" stays text (no locale guessing). Full CSV quoting is supported:
|
|
954
|
+
commas inside "quotes", escaped ""quotes"", and quoted fields spanning
|
|
955
|
+
multiple lines.
|
|
956
|
+
|
|
957
|
+
Number convention is fixed to comma-thousands / dot-decimal (1,000.50).
|
|
958
|
+
European-convention data (1.000,50; semicolon-delimited CSV) is not
|
|
959
|
+
supported - write numbers as 1000.50 in the source and the grid formats
|
|
960
|
+
them for display.
|
|
961
|
+
|
|
962
|
+
WRAPPING
|
|
963
|
+
A long value wraps once its column hits a width cap (rather than running
|
|
964
|
+
ever wider), growing the row. A literal <br> becomes a line break, and a
|
|
965
|
+
quoted cell with a real newline keeps it. Cell content is always plain
|
|
966
|
+
text - markup never renders.
|
|
967
|
+
|
|
968
|
+
FORMATTING
|
|
969
|
+
Numbers display with thousands separators and negatives in red by default.
|
|
970
|
+
An optional first line sets per-column formats by column letter:
|
|
971
|
+
|
|
972
|
+
\`\`\`cells
|
|
973
|
+
format: A=plain B=$ C=%
|
|
974
|
+
Year,Revenue,Margin
|
|
975
|
+
2024,12000,0.23
|
|
976
|
+
\`\`\`
|
|
977
|
+
|
|
978
|
+
Renders 2024 (plain - no comma), $12,000.00, 23%.
|
|
979
|
+
|
|
980
|
+
Format tokens (keyed by column letter):
|
|
981
|
+
$ / usd currency, e.g. $12,000.00 (also £ / gbp, € / eur)
|
|
982
|
+
% percent - multiplies by 100, e.g. 0.23 -> 23%
|
|
983
|
+
, / number thousands separators (the default)
|
|
984
|
+
plain / text no number formatting (good for years, ids, codes)
|
|
985
|
+
.N suffix fixed decimals, e.g. $.0 (no cents), %.1, .2
|
|
986
|
+
|
|
987
|
+
Formatting is display only - copy and export always emit the original
|
|
988
|
+
values. This is what makes a cells block more than a CSV: the author
|
|
989
|
+
chooses how each column reads.
|
|
990
|
+
|
|
991
|
+
FORMULAS
|
|
992
|
+
A cell whose value starts with = is a formula, evaluated in the browser.
|
|
993
|
+
The cell shows the result; the formula stays underneath (copy / export and
|
|
994
|
+
the formula bar show the =formula, hovering shows it as a tooltip).
|
|
995
|
+
|
|
996
|
+
\`\`\`cells
|
|
997
|
+
Item,Qty,Price,Total
|
|
998
|
+
Widget,10,12,=B2*C2
|
|
999
|
+
Gadget,15,8,=B3*C3
|
|
1000
|
+
Total,=SUM(B2:B3),,=SUM(D2:D3)
|
|
1001
|
+
\`\`\`
|
|
1002
|
+
|
|
1003
|
+
Supported:
|
|
1004
|
+
arithmetic + - * / ^ % with parentheses, e.g. =(A1+B1)*2
|
|
1005
|
+
references A1, B12; ranges A1:B3 inside a function
|
|
1006
|
+
aggregates SUM AVERAGE (AVG) MIN MAX COUNT COUNTA PRODUCT
|
|
1007
|
+
functions ROUND(x,n) ABS(x) IF(cond, a, b)
|
|
1008
|
+
comparisons = <> < <= > >= (mostly for IF)
|
|
1009
|
+
|
|
1010
|
+
A failed formula shows a short red error code: #DIV/0!, #NAME? (unknown
|
|
1011
|
+
name), #REF! (bad range), #VALUE! (e.g. text in arithmetic), #CIRC! (a
|
|
1012
|
+
circular reference). Formulas recalculate live while you edit.
|
|
1013
|
+
|
|
1014
|
+
MULTIPLE TABS (SHEETS)
|
|
1015
|
+
A document can hold several tabs that work together. Each tab is its own
|
|
1016
|
+
\`\`\`cells block; name it in the fence, right after the word cells:
|
|
1017
|
+
|
|
1018
|
+
\`\`\`cells Expenses
|
|
1019
|
+
Category,Jan,Feb,Mar
|
|
1020
|
+
Rent,1200,1200,1200
|
|
1021
|
+
Food,350,400,380
|
|
1022
|
+
Total,=SUM(B2:B3),=SUM(C2:C3),=SUM(D2:D3)
|
|
1023
|
+
\`\`\`
|
|
1024
|
+
|
|
1025
|
+
\`\`\`cells Summary
|
|
1026
|
+
Metric,Value
|
|
1027
|
+
Grand Total,=SUM(Expenses!B4:D4)
|
|
1028
|
+
\`\`\`
|
|
1029
|
+
|
|
1030
|
+
A formula reads another tab with a Sheet!A1 reference: =Expenses!B4 reads
|
|
1031
|
+
cell B4 of the Expenses tab, =SUM(Expenses!B4:D4) sums a range on it. A
|
|
1032
|
+
bare reference (=B4) always means the current tab. Names are
|
|
1033
|
+
case-insensitive; an unnamed block is Sheet1, Sheet2... by order; if two
|
|
1034
|
+
tabs share a name the first one owns it for references.
|
|
1035
|
+
|
|
1036
|
+
Qualified ranges stay within one tab (Expenses!A1:C1). A range that names
|
|
1037
|
+
two different tabs, a reference to a tab that does not exist, and a cycle
|
|
1038
|
+
that runs between tabs are all reported as errors (#REF! / #CIRC!), never a
|
|
1039
|
+
wrong number or a hang.
|
|
1040
|
+
|
|
1041
|
+
Two ways to view the tabs:
|
|
1042
|
+
- Stacked (default): each tab renders where its block sits, named by a
|
|
1043
|
+
small caption - good for a document with prose between the tabs.
|
|
1044
|
+
- One pane (opt in): expanding ANY tab opens the whole workbook in one
|
|
1045
|
+
window with a tab strip to switch sheets. For an in-document tabbed
|
|
1046
|
+
pane, add cells-tabs: tabbed to the document's front matter and the
|
|
1047
|
+
tabs collapse into a single widget (a tab strip + one grid at a time),
|
|
1048
|
+
placed where the first tab sat.
|
|
1049
|
+
|
|
1050
|
+
VERIFYING (for agents)
|
|
1051
|
+
Check the computed values without a browser:
|
|
1052
|
+
|
|
1053
|
+
sdoc cells verify <file.md> # values of every tab as CSV
|
|
1054
|
+
sdoc cells verify <file.md> --json # structured, lossless
|
|
1055
|
+
sdoc cells verify <file.md> --sheet Summary # one tab only
|
|
1056
|
+
|
|
1057
|
+
It runs the SAME engine the page does, so the numbers it prints are the
|
|
1058
|
+
numbers the document will show. Formula cells print their result; an
|
|
1059
|
+
errored cell prints its code (#REF! etc.) in place. The exit code is 0 when
|
|
1060
|
+
every tab computes cleanly and 1 when any cell errors, so an agent can gate
|
|
1061
|
+
on it. Write formulas, run verify, read the values back, fix, repeat.
|
|
1062
|
+
|
|
1063
|
+
The default output banners each tab with "# sheet: <name>". A data row
|
|
1064
|
+
could itself start with that text, so for machine parsing use --json (its
|
|
1065
|
+
per-tab values array is unambiguous).
|
|
1066
|
+
|
|
1067
|
+
SORTING
|
|
1068
|
+
Hover a column letter: an arrow appears on its right showing what a click
|
|
1069
|
+
will do (up = sort ascending, down = descending, x = clear the sort). The
|
|
1070
|
+
current sort shows as a colored arrow. A text first row is kept pinned as
|
|
1071
|
+
a header. Sorting is a view reorder - it does not change the document;
|
|
1072
|
+
copy reflects what is on screen.
|
|
1073
|
+
|
|
1074
|
+
Formula cells sort by their computed value, and that value travels with
|
|
1075
|
+
its row: references keep meaning what the author wrote, so a sorted
|
|
1076
|
+
=SUM(...) row never recomputes against the shuffled view.
|
|
1077
|
+
|
|
1078
|
+
A trailing summary row - one whose formula aggregates a range of other
|
|
1079
|
+
rows, like a Total row with =SUM(D2:D5) - is pinned to the bottom and
|
|
1080
|
+
stays there through any sort, the same way a header row stays at the top.
|
|
1081
|
+
Per-row formulas like =B2*C2 sort with the data as normal.
|
|
1082
|
+
|
|
1083
|
+
RESIZING
|
|
1084
|
+
Drag a column header's right edge to set an explicit width; it persists
|
|
1085
|
+
through sorts. Otherwise columns size to their content.
|
|
1086
|
+
|
|
1087
|
+
SELECTING
|
|
1088
|
+
Click a cell to select it; its column letter and row number light up.
|
|
1089
|
+
Click a column letter / row number to select the whole column / row.
|
|
1090
|
+
Drag to select a range; Shift+Click or Shift+Arrow extends it. Arrow
|
|
1091
|
+
keys move the selection; Cmd/Ctrl+Arrow jumps to the far edge;
|
|
1092
|
+
Shift+(Cmd/Ctrl+)Arrow extends to it. Dragging to the left/right edge of
|
|
1093
|
+
a wide sheet auto-scrolls. Esc - or clicking anywhere outside the sheet -
|
|
1094
|
+
clears the selection.
|
|
1095
|
+
|
|
1096
|
+
Selecting a multi-cell range opens a stats strip below the grid with the
|
|
1097
|
+
range's Sum / Avg / Min / Max / Count. Formula cells count by their
|
|
1098
|
+
computed value. It collapses when the selection is cleared or back to a
|
|
1099
|
+
single cell.
|
|
1100
|
+
|
|
1101
|
+
TOOLBAR
|
|
1102
|
+
A white bar sits above each sheet:
|
|
1103
|
+
- Left: the selection address (e.g. B3 or B2:C3) and, for a sheet
|
|
1104
|
+
loaded from a file, the source filename - "B3 · report.csv".
|
|
1105
|
+
- Right: a copy icon that copies the WHOLE sheet as CSV (values only,
|
|
1106
|
+
no row / column labels).
|
|
1107
|
+
- When something is selected, a second button copies just the
|
|
1108
|
+
selection ("cell" for one cell, "selection" for a range).
|
|
1109
|
+
- A fullscreen expand button opens the sheet as a full-window,
|
|
1110
|
+
editable spreadsheet (see FULLSCREEN & EDITING).
|
|
1111
|
+
|
|
1112
|
+
Copy buttons copy what the sheet shows: formula cells emit their
|
|
1113
|
+
computed value, not the "=..." text. In fullscreen, a sheet with
|
|
1114
|
+
formulas swaps the copy icon for a labelled "values" button and adds a
|
|
1115
|
+
"formulas" button beside it that copies the raw data - formulas as
|
|
1116
|
+
written, plain values elsewhere.
|
|
1117
|
+
|
|
1118
|
+
LOADING FROM A CSV FILE
|
|
1119
|
+
Reference a file instead of typing data inline:
|
|
1120
|
+
|
|
1121
|
+
\`\`\`cells
|
|
1122
|
+
{{path/to/report.csv}}
|
|
1123
|
+
\`\`\`
|
|
1124
|
+
|
|
1125
|
+
sdoc reads that file (resolved relative to the markdown document), reads
|
|
1126
|
+
the whole thing, and bakes the CSV straight into the document. The result
|
|
1127
|
+
is self-contained: a shared link shows the data, never a "file not
|
|
1128
|
+
found". Only the filename is recorded as the source (shown in the bar),
|
|
1129
|
+
not the full path.
|
|
1130
|
+
|
|
1131
|
+
- One reference per cells block (the block's entire body is the
|
|
1132
|
+
reference).
|
|
1133
|
+
- \`sdoc report.csv\` opens a CSV file directly as a sheet.
|
|
1134
|
+
|
|
1135
|
+
CSV FILES WITH FORMULAS
|
|
1136
|
+
A .csv file can hold =formulas in its cells, exactly like an inline
|
|
1137
|
+
block:
|
|
1138
|
+
|
|
1139
|
+
Item,Qty,Total
|
|
1140
|
+
Laptop,12,=B2*100
|
|
1141
|
+
Total,=SUM(B2:B2),=SUM(C2:C2)
|
|
1142
|
+
|
|
1143
|
+
\`sdoc that-file.csv\` opens it as a working sheet - the formulas
|
|
1144
|
+
compute - and the Excel download carries them as live formulas. This
|
|
1145
|
+
is the lowest-friction way for an agent to hand over a spreadsheet:
|
|
1146
|
+
write a plain CSV with formulas where computed values belong, run
|
|
1147
|
+
sdoc on it. Quote any formula containing commas ("=ROUND(B2, 1)") -
|
|
1148
|
+
standard CSV quoting. The file itself opens read-only; it stays the
|
|
1149
|
+
clean source of truth.
|
|
1150
|
+
|
|
1151
|
+
EXCEL EXPORT SECURITY
|
|
1152
|
+
Only formulas this sheet itself can compute (the FORMULAS list above)
|
|
1153
|
+
export as live Excel formulas. Anything else - Excel functions we do
|
|
1154
|
+
not support, and especially the CSV-injection attack class
|
|
1155
|
+
(WEBSERVICE, HYPERLINK, DDE) - exports as inert text. A document
|
|
1156
|
+
someone shares with you can never smuggle an executable formula into
|
|
1157
|
+
your downloaded workbook.
|
|
1158
|
+
|
|
1159
|
+
LIMITS
|
|
1160
|
+
- Per-block source-size cap and a per-document block cap.
|
|
1161
|
+
- The inline preview bounds very large grids and notes what it clipped
|
|
1162
|
+
("Showing 200 × 50 of 231 × 60 cells"); the full data still travels and
|
|
1163
|
+
copies.
|
|
1164
|
+
|
|
1165
|
+
FULLSCREEN & EDITING
|
|
1166
|
+
The expand button opens the sheet full-window: frozen headers, both-axis
|
|
1167
|
+
scroll, and a header bar with the selection address, its Sum / Avg /
|
|
1168
|
+
Min / Max / Count, and the formula bar. Formula cells count toward those
|
|
1169
|
+
stats by their computed value. The fullscreen sheet is editable (the
|
|
1170
|
+
inline one stays read-only):
|
|
1171
|
+
- Double-click a cell, or just start typing, to edit it.
|
|
1172
|
+
- Enter / Tab (and Shift+ to go back) commit and move on.
|
|
1173
|
+
- The formula bar edits the active cell; type a value or =formula, Enter.
|
|
1174
|
+
- Delete / Backspace clears the selected cell(s).
|
|
1175
|
+
- Cmd/Ctrl+Z undoes, Shift+Cmd/Ctrl+Z (or Ctrl+Y) redoes.
|
|
1176
|
+
- Paste TSV or CSV to drop a block of values in at the selection.
|
|
1177
|
+
- Point mode: while typing a formula, arrow keys point at cells and
|
|
1178
|
+
write their reference into the formula. Type =SUM( then arrow to a
|
|
1179
|
+
cell, hold Shift+Arrow to grow it into a range, type ) and Enter.
|
|
1180
|
+
Or arrow to the range start, type :, arrow to the end. Works in the
|
|
1181
|
+
cell editor and the formula bar; the pointed cells highlight violet.
|
|
1182
|
+
- Fill handle: drag the small square on the selection's corner to fill
|
|
1183
|
+
neighbouring cells. Formulas shift their references (=B2*C2 becomes
|
|
1184
|
+
=B3*C3 one row down), values repeat, and a run of numbers (1, 2)
|
|
1185
|
+
continues as a series (3, 4...).
|
|
1186
|
+
- Copy / paste formulas: Cmd/Ctrl+C copies the selection (formulas
|
|
1187
|
+
included); pasting back in adjusts each formula's references for
|
|
1188
|
+
where it lands. Copy one formula, select a range, paste - every cell
|
|
1189
|
+
gets the formula relative to its own row/column.
|
|
1190
|
+
- Formula view: when a sheet has formulas, an =fx button in the top bar
|
|
1191
|
+
switches every formula cell to show its source ("=B2*C2") instead of
|
|
1192
|
+
its value, editable in place. Click again for values.
|
|
1193
|
+
Editing is client-side and ephemeral: changes show in the inline grid when
|
|
1194
|
+
you close, but nothing is written back to the document or to any file.
|
|
1195
|
+
|
|
1196
|
+
After a fullscreen edit, the inline toolbar shows a "showing edited" pill:
|
|
1197
|
+
the grid displays your edits, the document is unchanged. Click the pill to
|
|
1198
|
+
flip to the document's original data ("showing original") and back.
|
|
1199
|
+
Expanding again always resumes from the edits.
|
|
1200
|
+
|
|
1201
|
+
EXPORT
|
|
1202
|
+
HTML / Word / PDF export emit a real table of the values (the source
|
|
1203
|
+
label and the row / column chrome are dropped).
|
|
1204
|
+
|
|
1205
|
+
The download button in a sheet's toolbar (inline and fullscreen) exports
|
|
1206
|
+
that sheet as an Excel workbook (.xlsx). Formulas export as live Excel
|
|
1207
|
+
formulas and recalculate when the file opens; the format: directive's
|
|
1208
|
+
currency / percent / comma columns carry over as Excel number formats.
|
|
1209
|
+
The export uses the document's row order (plus any fullscreen edits),
|
|
1210
|
+
never the sorted view, so formula references stay correct.
|
|
1211
|
+
`;
|
|
1212
|
+
|
|
1213
|
+
const SLIDES_HELP = `
|
|
1214
|
+
SmallDocs — Slides
|
|
1215
|
+
==============
|
|
1216
|
+
Embed presentation slides in any markdown document using fenced
|
|
1217
|
+
slide blocks. Slides render as thumbnails inline; click the small
|
|
1218
|
+
present-mode icon in the slide's top-right corner to enter fullscreen.
|
|
1219
|
+
Text inside a slide thumbnail is selectable. Esc to exit, arrows to
|
|
1220
|
+
navigate.
|
|
1221
|
+
|
|
1222
|
+
\u2500\u2500 COMMANDS \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\u2500\u2500\u2500\u2500\u2500
|
|
1223
|
+
sdoc present <file> Open file directly in fullscreen slide view
|
|
1224
|
+
sdoc <file> Open normally (click a slide's present icon)
|
|
1225
|
+
sdoc slides This help
|
|
1226
|
+
sdoc slides list List built-in templates + slot names
|
|
1227
|
+
sdoc slides icons [query] List the Lucide icons available to the
|
|
1228
|
+
\`icon\` shape kind. Optional substring filter.
|
|
1229
|
+
sdoc slides custom-shapes Long-tail notes for raw-shape custom slides
|
|
1230
|
+
(polygon text, composite patterns, layering).
|
|
1231
|
+
Most decks use \`@extends\` and never need this.
|
|
1232
|
+
|
|
1233
|
+
── DESIGN GUIDELINES ────────────────────────────────────
|
|
1234
|
+
The built-in templates encode a few rules that separate professional
|
|
1235
|
+
slides from amateur ones. When you compose slides by hand (no
|
|
1236
|
+
template), keep these in mind - they're the difference between a
|
|
1237
|
+
deck that lands and one that doesn't.
|
|
1238
|
+
|
|
1239
|
+
Margins. Keep all content inside a 1-unit safe area on every side
|
|
1240
|
+
of a 16x9 grid (so x ∈ [1, 15], y ∈ [0.5, 8.5]). Nothing touches
|
|
1241
|
+
the slide edge except a deliberate full-bleed background (\`section\`
|
|
1242
|
+
uses this; nothing else should).
|
|
1243
|
+
|
|
1244
|
+
No auto-flow. Every shape claims its full (x, y, w, h) rect.
|
|
1245
|
+
Source order is paint order, so a later filled rectangle that
|
|
1246
|
+
overlaps an earlier text shape's bbox covers its content. Place
|
|
1247
|
+
each shape at the previous shape's bottom edge (y + h) or later.
|
|
1248
|
+
Common slip: putting content at y=12 right under
|
|
1249
|
+
\`r 6 9 88 5 text=subtitle ...\` - the subtitle reserves y=9..14;
|
|
1250
|
+
starting the body at y=12 puts the fill on top of the subtitle's
|
|
1251
|
+
bottom half. For \`text=title\` and \`text=subtitle\` add another
|
|
1252
|
+
~0.5 grid unit on top to clear glyph descenders; see
|
|
1253
|
+
\`slides custom-shapes\` for the fine print.
|
|
1254
|
+
|
|
1255
|
+
No fill colours behind body or title text. The slide background IS
|
|
1256
|
+
your canvas. Saturated rectangles compete with content and read as
|
|
1257
|
+
"PowerPoint 2003". The only exception: section dividers, which want
|
|
1258
|
+
contrast against content slides - use \`grid bg=\` there, not a
|
|
1259
|
+
shape \`fill=\`.
|
|
1260
|
+
|
|
1261
|
+
Two or three sizes per slide, max. Stick to the role table:
|
|
1262
|
+
text=title (64px) for cover, quote, section, metric
|
|
1263
|
+
text=subtitle (40px) for in-deck content-slide titles
|
|
1264
|
+
text=body (24px) default; bullets, paragraphs
|
|
1265
|
+
text=caption (14px) ONLY for footers, eyebrows, attributions
|
|
1266
|
+
Caption renders as ~3px in a 240px-wide thumbnail - never put
|
|
1267
|
+
load-bearing content in caption role.
|
|
1268
|
+
|
|
1269
|
+
Default \`valign=center\`. \`valign=top\` reads right only when the
|
|
1270
|
+
body shape is sized to its content. On an oversized body shape
|
|
1271
|
+
top-anchoring leaves dead space underneath; centering balances it.
|
|
1272
|
+
|
|
1273
|
+
Content fills 55-65% of the safe area, no more. Empty space is a
|
|
1274
|
+
feature: it's what makes a deck feel confident rather than crowded.
|
|
1275
|
+
If a shape is mostly empty, shrink the shape - don't fill it.
|
|
1276
|
+
|
|
1277
|
+
Action titles versus topic titles. An action title states the
|
|
1278
|
+
claim ("Method X reduced error 40%") and reads like prose. A topic
|
|
1279
|
+
title labels what's below ("Methodology"). Both are fine; pick
|
|
1280
|
+
one. Action titles tend to wrap to two lines, which is why
|
|
1281
|
+
in-deck titles use subtitle role (40px) not title role (64px).
|
|
1282
|
+
|
|
1283
|
+
Body bullets should be parallel. If your bullets don't read like
|
|
1284
|
+
a list - if the items have different shapes, weights, or
|
|
1285
|
+
connective tissue (because, but, so) - write a sentence instead.
|
|
1286
|
+
A bulleted paragraph is hiding the fact that you haven't decided
|
|
1287
|
+
what you're claiming.
|
|
1288
|
+
|
|
1289
|
+
When in doubt, \`@extends\` a built-in template instead of
|
|
1290
|
+
composing from raw shapes. Run \`sdoc slides list\` to see the
|
|
1291
|
+
registry.
|
|
1292
|
+
|
|
1293
|
+
\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
|
|
1294
|
+
Wrap shape DSL in a ~~~slide fenced block:
|
|
1295
|
+
|
|
1296
|
+
~~~slide
|
|
1297
|
+
grid 100 56.25
|
|
1298
|
+
r 5 5 90 15 fill=#1e40af color=#fff text=title | Q4 Review
|
|
1299
|
+
r 5 22 42 26 align=left |
|
|
1300
|
+
## Wins
|
|
1301
|
+
- Shipped slides
|
|
1302
|
+
- Tilde fences
|
|
1303
|
+
~~~
|
|
1304
|
+
|
|
1305
|
+
Tildes (not backticks) so shape content can include \`\`\` code blocks
|
|
1306
|
+
without closing the fence early. Triple-backtick \`\`\`slide also works
|
|
1307
|
+
(marked accepts either) - but prefer tildes so a nested \`\`\`python
|
|
1308
|
+
inside a shape doesn't end the slide block prematurely.
|
|
1309
|
+
|
|
1310
|
+
\u2500\u2500 GRID \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1311
|
+
First line: \`grid W H [key=val ...]\`. Default 100 56.25 (16:9).
|
|
1312
|
+
All shape coordinates are in grid units.
|
|
1313
|
+
grid 100 56.25
|
|
1314
|
+
grid 100 75 (4:3)
|
|
1315
|
+
grid 100 100 (square)
|
|
1316
|
+
grid 100 56.25 bg=#0f172a (set slide background color)
|
|
1317
|
+
|
|
1318
|
+
W x H defines the aspect ratio and coordinate system, not a pixel size.
|
|
1319
|
+
Slides fill whatever space they're rendered into - inline thumbnail in
|
|
1320
|
+
a doc, small rail thumbnail in present mode, fullscreen stage, PDF page -
|
|
1321
|
+
and text auto-fits via container queries. Pick numbers for the aspect
|
|
1322
|
+
ratio you want; 100 on one axis is the convention, making the other
|
|
1323
|
+
axis a simple percentage.
|
|
1324
|
+
|
|
1325
|
+
\u2500\u2500 RAW SHAPES \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\u2500
|
|
1326
|
+
Templates encode their layouts in shape DSL (\`r\` / \`p\` / \`c\` /
|
|
1327
|
+
\`e\` / \`l\` / \`a\` / \`chev\` / \`bub\` / \`cyl\`). As a consumer of a
|
|
1328
|
+
template, you don't see these - you fill slots via \`#name: value\`
|
|
1329
|
+
(see TEMPLATES below). If you're defining your own \`@template\`,
|
|
1330
|
+
or composing a custom slide from raw shapes, run:
|
|
1331
|
+
|
|
1332
|
+
sdoc slides custom-shapes Shape kinds, ids / @refs, layering,
|
|
1333
|
+
polygon gotchas, composite patterns
|
|
1334
|
+
|
|
1335
|
+
Three high-level shapes (\`chev\`, \`bub\`, \`cyl\`) ship with their
|
|
1336
|
+
text-centring rect already tuned to the visual body, not the
|
|
1337
|
+
bounding box. Reach for them before composing the equivalent
|
|
1338
|
+
polygon by hand - they save lines AND get the text positioning
|
|
1339
|
+
right by default.
|
|
1340
|
+
|
|
1341
|
+
\u2500\u2500 ICONS \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1342
|
+
The \`icon\` shape kind renders an inline outline icon from the
|
|
1343
|
+
bundled Lucide library (https://lucide.dev/icons/, ~1960 icons).
|
|
1344
|
+
Use it to mark concepts on a slide - a user, a database, a cloud,
|
|
1345
|
+
a lock - without resorting to emoji or raster images.
|
|
1346
|
+
|
|
1347
|
+
icon 8 30 6 6 name=user color=#0F1E3A
|
|
1348
|
+
icon 20 30 6 6 name=database
|
|
1349
|
+
icon 32 30 6 6 name=cloud-upload strokeWidth=2
|
|
1350
|
+
|
|
1351
|
+
Unknown names render a pink struck-through placeholder, so typos
|
|
1352
|
+
are visible rather than silent. To find an icon name, run:
|
|
1353
|
+
|
|
1354
|
+
sdoc slides icons List every available name
|
|
1355
|
+
sdoc slides icons cloud Filter by substring
|
|
1356
|
+
|
|
1357
|
+
Full attribute reference (color, strokeWidth, lazy-load behaviour)
|
|
1358
|
+
lives in \`sdoc slides custom-shapes\` under the \`icon\` shape entry.
|
|
1359
|
+
|
|
1360
|
+
\u2500\u2500 SHAPE ATTRIBUTES \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
|
|
1361
|
+
Between geometry and \`|\`:
|
|
1362
|
+
|
|
1363
|
+
The text-related attributes below apply to BOTH the shapes a
|
|
1364
|
+
template defines and the slot content you write as a consumer.
|
|
1365
|
+
Visual attributes (fill, stroke, strokeWidth, radius, etc.) are
|
|
1366
|
+
documented in \`sdoc slides custom-shapes\`.
|
|
1367
|
+
|
|
1368
|
+
Padding:
|
|
1369
|
+
padding=N Inner padding in grid units (0 disables). On a
|
|
1370
|
+
100-wide grid, \`padding=2\` is ~2% of slide width.
|
|
1371
|
+
|
|
1372
|
+
Alignment:
|
|
1373
|
+
align=<a> Horizontal: center (default), left, right
|
|
1374
|
+
valign=<v> Vertical: center (default), top, bottom
|
|
1375
|
+
|
|
1376
|
+
Text body box:
|
|
1377
|
+
textBox=x,y,w,h Override the rectangle that holds the shape's
|
|
1378
|
+
text content. Values are grid units, relative
|
|
1379
|
+
to the shape's bounding-box top-left. The shape
|
|
1380
|
+
itself (fill, stroke, geometry) is unchanged -
|
|
1381
|
+
only the text inside shifts. Use when the
|
|
1382
|
+
shape is asymmetric and centering text in its
|
|
1383
|
+
bounding box drifts the text off the visual
|
|
1384
|
+
mass.
|
|
1385
|
+
|
|
1386
|
+
The classic case: a right-pointing chevron
|
|
1387
|
+
polygon. Its bounding box includes the tip,
|
|
1388
|
+
so centred text sits to the right of the
|
|
1389
|
+
chevron body. Set textBox to the body region
|
|
1390
|
+
only:
|
|
1391
|
+
|
|
1392
|
+
p 6,20 19,20 22,24 19,28 6,28 9,24
|
|
1393
|
+
fill=#4f8aff textBox=0,0,14,8
|
|
1394
|
+
color=#fff text=subtitle | Tokenize
|
|
1395
|
+
|
|
1396
|
+
Bbox of the polygon is (6,20)-(22,28). The
|
|
1397
|
+
body (excluding the 3u tip) is 13 wide x 8
|
|
1398
|
+
tall starting at (6,20); textBox=0,0,14,8 puts
|
|
1399
|
+
text inside that region.
|
|
1400
|
+
|
|
1401
|
+
Text rotation:
|
|
1402
|
+
textAngle=N Rotate the text WITHIN the shape by N degrees.
|
|
1403
|
+
The shape's geometry (x, y, w, h) is untouched -
|
|
1404
|
+
only the text inside rotates. Useful for vertical
|
|
1405
|
+
axis labels, diagonal stamps, and skewed callouts.
|
|
1406
|
+
Default 0 (no rotation).
|
|
1407
|
+
|
|
1408
|
+
Cardinal angles (0, 90, -90, 180) "just work":
|
|
1409
|
+
- 90 / -90: text is laid out using the shape's
|
|
1410
|
+
SWAPPED dimensions, so "More autonomous" in a
|
|
1411
|
+
tall narrow shape wraps along the long axis
|
|
1412
|
+
and reads top-to-bottom (90) or
|
|
1413
|
+
bottom-to-top (-90).
|
|
1414
|
+
- 180: text is flipped in place.
|
|
1415
|
+
|
|
1416
|
+
Non-cardinal angles (e.g. textAngle=37) render
|
|
1417
|
+
but get NO dimension swap and NO autofit help.
|
|
1418
|
+
The text is rotated around the shape's centre;
|
|
1419
|
+
its visible extent will usually exceed the
|
|
1420
|
+
shape's (w, h) box. Size the container so the
|
|
1421
|
+
rotated text lands where you want:
|
|
1422
|
+
rotatedW = |w * cos N| + |h * sin N|
|
|
1423
|
+
rotatedH = |w * sin N| + |h * cos N|
|
|
1424
|
+
The shape's own bbox doesn't change - only the
|
|
1425
|
+
rendered text sticks out. Place neighbouring
|
|
1426
|
+
shapes accordingly, or accept the overlap as
|
|
1427
|
+
intentional.
|
|
1428
|
+
|
|
1429
|
+
Example (vertical y-axis label):
|
|
1430
|
+
r 8 16 4 32 textAngle=-90 text=caption
|
|
1431
|
+
align=center | More autonomous
|
|
1432
|
+
|
|
1433
|
+
Text sizing (role first, escape hatches second):
|
|
1434
|
+
text=<role> Pick a role from a fixed table. Roles give a
|
|
1435
|
+
deck consistent typography. Default is \`body\`.
|
|
1436
|
+
text=title 64px (slide titles)
|
|
1437
|
+
text=subtitle 40px (section heads, sub-titles)
|
|
1438
|
+
text=body 24px (default; paragraphs, bullets)
|
|
1439
|
+
text=caption 14px (footnotes, fine print)
|
|
1440
|
+
Unknown roles fall back to \`body\` silently.
|
|
1441
|
+
|
|
1442
|
+
size=Npx Literal size override; takes precedence over the
|
|
1443
|
+
role. Units: px | pt | em | rem (bare number = px).
|
|
1444
|
+
Use sparingly - the role table is what keeps the
|
|
1445
|
+
deck rhythm consistent.
|
|
1446
|
+
size=fit Opt into auto-fit: binary search for the largest
|
|
1447
|
+
font that fits the shape, capped at 12% of stage
|
|
1448
|
+
height (or the per-shape maxfont= value).
|
|
1449
|
+
|
|
1450
|
+
maxfont=Npx Caps \`size=fit\` higher or lower than the default
|
|
1451
|
+
stage cap. Has no effect when size= isn't \`fit\`.
|
|
1452
|
+
|
|
1453
|
+
Px values size as if the stage were 720px tall and scale proportionally
|
|
1454
|
+
in smaller views (rail thumbnails, inline thumbs), so \`size=18px\` reads
|
|
1455
|
+
as "18px on a fullscreen slide".
|
|
1456
|
+
|
|
1457
|
+
A deck that uses only roles (no \`size=\`) lands at 2-3 distinct font
|
|
1458
|
+
sizes across all slides, which is what makes presentations look
|
|
1459
|
+
professional. Reach for \`size=Npx\` only for hero numbers or other
|
|
1460
|
+
one-off treatments.
|
|
1461
|
+
|
|
1462
|
+
Per-element scale (applied inside the shape's shadow root):
|
|
1463
|
+
h1Scale=N h1 is N\u00d7 the shape's resolved font size.
|
|
1464
|
+
h2Scale=N h2 is N\u00d7 the shape's resolved font size.
|
|
1465
|
+
h3Scale=N, h4Scale=N, h5Scale=N, h6Scale=N
|
|
1466
|
+
pScale=N Scale paragraph text (default 1).
|
|
1467
|
+
|
|
1468
|
+
Each scale affects ONLY that element type. \`h1Scale=3\` enlarges
|
|
1469
|
+
h1 headings, leaves paragraphs alone. The shape's resolved font
|
|
1470
|
+
size (the role's px from \`text=\`, the \`size=Npx\` override, or the
|
|
1471
|
+
autofit output when \`size=fit\`) is the base for the multiplier.
|
|
1472
|
+
|
|
1473
|
+
Defaults without overrides: h1 1.4, h2 1.2, h3 1.05, h4-h6 1.0,
|
|
1474
|
+
p 1.0. Note that h4/h5/h6 render at the SAME size by default, so
|
|
1475
|
+
they don't give you three-step hierarchy out of the box - use
|
|
1476
|
+
explicit h4Scale/h5Scale/h6Scale if you need a h4>h5>h6 spread.
|
|
1477
|
+
|
|
1478
|
+
When to use: one shape holds mixed content (heading + body) and
|
|
1479
|
+
you want the heading BIGGER or the body SMALLER than the 1.4 / 1.0
|
|
1480
|
+
default. Two common patterns:
|
|
1481
|
+
|
|
1482
|
+
Hero number (giant + tiny caption, one shape):
|
|
1483
|
+
r 10 15 80 30 h1Scale=3 pScale=0.4 |
|
|
1484
|
+
# 87%
|
|
1485
|
+
of teams ship faster with one-page decks
|
|
1486
|
+
|
|
1487
|
+
Quote card (prominent body, small attribution):
|
|
1488
|
+
r 10 15 80 30 h2Scale=0.5 |
|
|
1489
|
+
Quote text in body size here.
|
|
1490
|
+
## - Attribution
|
|
1491
|
+
|
|
1492
|
+
Invalid or \u2264 0 values are ignored (fallback to defaults).
|
|
1493
|
+
|
|
1494
|
+
Identification:
|
|
1495
|
+
#id Reference target for @refs
|
|
1496
|
+
|
|
1497
|
+
Stacking:
|
|
1498
|
+
layer=<v> top | mid | bottom. Default \`mid\` for every
|
|
1499
|
+
kind. Source order alone normally decides paint
|
|
1500
|
+
order; \`layer=\` is an escape hatch for shapes
|
|
1501
|
+
that must sit on top of (or below) everything
|
|
1502
|
+
regardless of where they were declared. See
|
|
1503
|
+
STACKING section below.
|
|
1504
|
+
|
|
1505
|
+
\u2500\u2500 CONTENT \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1506
|
+
Everything after \`|\` is standard markdown. Multi-line uses
|
|
1507
|
+
indentation under the shape line - continuation lines MUST be
|
|
1508
|
+
indented at least 2 spaces, or the parser treats them as fresh
|
|
1509
|
+
top-level shape lines (and fails).
|
|
1510
|
+
|
|
1511
|
+
r 5 20 90 60 align=left |
|
|
1512
|
+
## Heading
|
|
1513
|
+
Some body paragraph.
|
|
1514
|
+
|
|
1515
|
+
- list item one
|
|
1516
|
+
- list item two
|
|
1517
|
+
|
|
1518
|
+
\`\`\`python
|
|
1519
|
+
def hi():
|
|
1520
|
+
print("hello")
|
|
1521
|
+
\`\`\`
|
|
1522
|
+
|
|
1523
|
+
Prefer putting the \`|\` on its own line (empty) with all content
|
|
1524
|
+
indented below. Mixing "first line after |" with unindented lines
|
|
1525
|
+
is a common parser error.
|
|
1526
|
+
|
|
1527
|
+
\u2500\u2500 ALIGNMENT GUIDELINES \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
|
|
1528
|
+
Default: \`align=center valign=center\`. Good for:
|
|
1529
|
+
- Title shapes (one heading or phrase)
|
|
1530
|
+
- Subheaders, section labels
|
|
1531
|
+
- Standalone caption text
|
|
1532
|
+
|
|
1533
|
+
Switch to \`align=left\` when the shape holds body copy - paragraphs,
|
|
1534
|
+
bullet lists, numbered lists, blockquotes. Left-aligned reads better
|
|
1535
|
+
once you have multiple lines. Keep \`valign=center\` (the default) so
|
|
1536
|
+
the block floats in the middle of the shape; switch to \`valign=top\`
|
|
1537
|
+
only when the shape is sized exactly to the content and you want it
|
|
1538
|
+
anchored to the top.
|
|
1539
|
+
|
|
1540
|
+
Rule of thumb:
|
|
1541
|
+
ONE short phrase \u2192 leave centered
|
|
1542
|
+
MULTIPLE lines \u2192 align=left, keep valign=center
|
|
1543
|
+
|
|
1544
|
+
\u2500\u2500 PULLING FROM DOC STYLES \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
|
|
1545
|
+
Slides pick up the host document's styles so a deck feels visually
|
|
1546
|
+
part of its doc. Two mechanisms, both resolved at render time against
|
|
1547
|
+
the active theme (so dark mode just works).
|
|
1548
|
+
|
|
1549
|
+
AUTOMATIC INHERITANCE (no DSL needed)
|
|
1550
|
+
- Slide background = styles.background (unless grid has bg=)
|
|
1551
|
+
- Body font = styles.fontFamily
|
|
1552
|
+
- Heading fonts = styles.headers.fontFamily
|
|
1553
|
+
- Code / pre / link = their respective styles.* values
|
|
1554
|
+
- Heading text inside a shape's markdown content adopts the doc's
|
|
1555
|
+
h1/h2/h3/h4 color. Example: \`# Title\` in a shape uses styles.h1.color.
|
|
1556
|
+
A shape's own \`color=\` always overrides this.
|
|
1557
|
+
|
|
1558
|
+
EXPLICIT REFERENCES ($path.to.prop)
|
|
1559
|
+
Any shape or grid attribute value can be a \`\$path.to.prop\` token,
|
|
1560
|
+
which resolves to the doc's live value for that style. Common cases:
|
|
1561
|
+
\`fill=\$h1.color\`, \`color=\$chart.accent\`, grid \`bg=\$background\`.
|
|
1562
|
+
Vocabulary = the YAML schema (run \`sdoc schema\`).
|
|
1563
|
+
|
|
1564
|
+
r 5 5 90 15 fill=\$h1.color color=#fff text=title | Title
|
|
1565
|
+
r 5 25 90 25 color=\$chart.accent | ## 40% growth
|
|
1566
|
+
grid 100 56.25 bg=\$blocks.background (subtle block-tinted slide)
|
|
1567
|
+
|
|
1568
|
+
Supported paths:
|
|
1569
|
+
\$background \$color \$fontFamily
|
|
1570
|
+
\$h.color \$h1.color \$h2.color \$h3.color \$h4.color
|
|
1571
|
+
\$headers.color \$headers.fontFamily
|
|
1572
|
+
\$p.color \$list.color \$link.color
|
|
1573
|
+
\$blocks.background \$blocks.color
|
|
1574
|
+
\$code.background \$code.color \$code.font
|
|
1575
|
+
\$blockquote.background \$blockquote.color \$blockquote.borderColor
|
|
1576
|
+
\$chart.accent \$chart.background \$chart.textColor
|
|
1577
|
+
|
|
1578
|
+
Unknown paths surface in the error badge with the rest of the
|
|
1579
|
+
diagnostics. Literal hex (#1e40af) still works when you want a
|
|
1580
|
+
one-off color that isn't in the doc's styles.
|
|
1581
|
+
|
|
1582
|
+
Blockquote-style card (tinted bg + left accent border): use a
|
|
1583
|
+
markdown \`>\` inside any shape. The quote picks up the doc's
|
|
1584
|
+
blockquote styling automatically - no \`stroke=\` workaround needed.
|
|
1585
|
+
r 50 15 42 30 padding=3 |
|
|
1586
|
+
> Our customers are the product
|
|
1587
|
+
>
|
|
1588
|
+
> - Jordan, CEO
|
|
1589
|
+
|
|
1590
|
+
\u2500\u2500 IMAGES \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\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1591
|
+
Any shape (\`r\`, \`c\`, \`p\`) can hold a bitmap via \`image=<url>\`.
|
|
1592
|
+
The URL accepts whatever the browser would load for a standard
|
|
1593
|
+
markdown \`![]()\`:
|
|
1594
|
+
|
|
1595
|
+
- \`data:\` URIs inlined in the DSL, no network fetch
|
|
1596
|
+
- \`https://\` URLs fetched at render time (CORS applies)
|
|
1597
|
+
|
|
1598
|
+
SmallDocs does not host image bytes. Data URIs live in your document
|
|
1599
|
+
(and share URL); external URLs fetch from whatever host you pick.
|
|
1600
|
+
|
|
1601
|
+
Attributes (apply to every shape kind):
|
|
1602
|
+
|
|
1603
|
+
image=<url> the bitmap source
|
|
1604
|
+
imageFit=cover|contain cover (default) fills the shape box and
|
|
1605
|
+
crops overflow; contain preserves aspect
|
|
1606
|
+
and letterboxes inside the shape
|
|
1607
|
+
imagePos=center|top|bottom|left|right
|
|
1608
|
+
which edge is pinned when cover crops or
|
|
1609
|
+
contain letterboxes; default center
|
|
1610
|
+
|
|
1611
|
+
Stacking inside a shape (bottom to top):
|
|
1612
|
+
1. \`fill=\` colour backdrop
|
|
1613
|
+
2. \`image=\` bitmap (shows fill through image alpha / on load fail)
|
|
1614
|
+
3. \`stroke=\` border
|
|
1615
|
+
4. \`| content\` markdown text
|
|
1616
|
+
|
|
1617
|
+
Examples:
|
|
1618
|
+
|
|
1619
|
+
# Small corner logo on a rect
|
|
1620
|
+
r 14 0.5 1.5 1 image=https://lucide.dev/logo.light.svg imageFit=contain
|
|
1621
|
+
|
|
1622
|
+
# Full-bleed hero with a title overlay (one shape, not two)
|
|
1623
|
+
r 0 0 16 9 image=data:image/png;base64,iVBORw0K... align=left valign=bottom padding=0.5 |
|
|
1624
|
+
# Q4 review
|
|
1625
|
+
|
|
1626
|
+
# Photo clipped inside a hand-drawn polygon
|
|
1627
|
+
p 3,1 13,1 14,5 10,8 6,8 2,5 image=https://example.com/team.jpg
|
|
1628
|
+
|
|
1629
|
+
# Circle avatar with a gold ring (stroke paints above image)
|
|
1630
|
+
c 8 4.5 2.2 image=/avatar.jpg stroke=#d4af37 strokeWidth=0.12
|
|
1631
|
+
|
|
1632
|
+
Shorthand: \`i x y w h src=<url>\` is parser sugar for
|
|
1633
|
+
\`r x y w h image=<url>\`, a friendly keystroke for the common case.
|
|
1634
|
+
\`src=\` is treated as a valid alias for \`image=\` everywhere.
|
|
1635
|
+
|
|
1636
|
+
PDF export embeds PNG and JPEG natively. SVG / WebP / GIF are
|
|
1637
|
+
skipped silently (console warning is logged). External URL fetches
|
|
1638
|
+
need CORS headers on the host, same constraint as any browser fetch.
|
|
1639
|
+
|
|
1640
|
+
\u2500\u2500 STACKING \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\u2500\u2500\u2500\u2500\u2500
|
|
1641
|
+
Source order = paint order. A shape declared later in the slide
|
|
1642
|
+
paints above shapes declared earlier - regardless of whether it is
|
|
1643
|
+
a rectangle, polygon, circle, line, or arrow. To put a connector
|
|
1644
|
+
above its cards, declare the arrow after the cards. To put a
|
|
1645
|
+
backdrop behind a card, declare the backdrop first.
|
|
1646
|
+
|
|
1647
|
+
Escape hatch: \`layer=top | mid | bottom\` (default \`mid\`) promotes
|
|
1648
|
+
or demotes a shape across coarse sublayers, overriding source order.
|
|
1649
|
+
Useful inside templates where a consumer slide adds more shapes
|
|
1650
|
+
whose declaration order you cannot predict. See the LAYERING
|
|
1651
|
+
section of \`sdoc slides custom-shapes\` for the full model.
|
|
1652
|
+
|
|
1653
|
+
\u2500\u2500 TEMPLATES \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\u2500\u2500\u2500\u2500
|
|
1654
|
+
Define a shape layout once, reuse across slides. Two directives,
|
|
1655
|
+
each must be the first non-blank line inside a slide block:
|
|
1656
|
+
|
|
1657
|
+
@template NAME Register this slide's DSL as a template.
|
|
1658
|
+
The slide DOES NOT RENDER - it's a definition.
|
|
1659
|
+
@extends NAME Inherit NAME's shapes; fill slot content below.
|
|
1660
|
+
|
|
1661
|
+
Shapes in the template carry an \`#id\` to mark slots. Consumers
|
|
1662
|
+
override those slots with \`#id: value\` blocks (inline for a single
|
|
1663
|
+
line, colon-only + following lines for multi-line content).
|
|
1664
|
+
|
|
1665
|
+
Author ordering doesn't matter - the consumer can appear before or
|
|
1666
|
+
after the template in the document. Templates never render, so they
|
|
1667
|
+
don't show up in the thumbnail flow or present mode.
|
|
1668
|
+
|
|
1669
|
+
Example:
|
|
1670
|
+
|
|
1671
|
+
~~~slide
|
|
1672
|
+
@template title-body
|
|
1673
|
+
grid 16 9
|
|
1674
|
+
r 0 0 16 3 #title fill=\$h1.color color=#fff text=title | placeholder title
|
|
1675
|
+
r 0 3 16 6 #body align=left |
|
|
1676
|
+
placeholder body
|
|
1677
|
+
~~~
|
|
1678
|
+
|
|
1679
|
+
~~~slide
|
|
1680
|
+
@extends title-body
|
|
1681
|
+
#title: What is SmallDocs?
|
|
1682
|
+
#body:
|
|
1683
|
+
- Markdown in, styled docs out
|
|
1684
|
+
- No server, hash-only state
|
|
1685
|
+
- Slides from fenced blocks
|
|
1686
|
+
~~~
|
|
1687
|
+
|
|
1688
|
+
~~~slide
|
|
1689
|
+
@extends title-body
|
|
1690
|
+
#title: Why templates
|
|
1691
|
+
#body: Define shape once, fill slots N times. Recolor once in front
|
|
1692
|
+
matter and every slide that uses the template picks it up.
|
|
1693
|
+
~~~
|
|
1694
|
+
|
|
1695
|
+
Partial fills: if a consumer omits an optional slot (provides
|
|
1696
|
+
\`#title\` but not \`#body\`), the template's placeholder content
|
|
1697
|
+
stays - so templates are self-documenting when first authored.
|
|
1698
|
+
|
|
1699
|
+
Required slots: mark a slot with a trailing \`!\` (e.g. \`#title!\`)
|
|
1700
|
+
in the template. If a consumer omits a required slot, the resolver
|
|
1701
|
+
surfaces an error in the slide's error badge. Optional slots have
|
|
1702
|
+
no marker.
|
|
1703
|
+
|
|
1704
|
+
Unknown template names, slots that don't match any shape id in the
|
|
1705
|
+
template, and missing required slots all surface in the error badge
|
|
1706
|
+
alongside any DSL errors.
|
|
1707
|
+
|
|
1708
|
+
Deliberately simple in v1: no attribute overrides (can't change
|
|
1709
|
+
\`fill=\` per consumer - fork the template if you need variants),
|
|
1710
|
+
no nested templates (a consumer can't extend another consumer).
|
|
1711
|
+
|
|
1712
|
+
── BUILT-IN TEMPLATES ───────────────────────────────────
|
|
1713
|
+
SmallDocs ships a small library of opinionated templates. \`@extends\`
|
|
1714
|
+
any of them without declaring a user \`@template\` first. Run
|
|
1715
|
+
\`sdoc slides list\` for the names + slot lists; the templates all
|
|
1716
|
+
use a 1-unit safe margin and avoid full-bleed coloured bars (the
|
|
1717
|
+
one exception is \`section\`, which uses \`grid bg=\` for contrast
|
|
1718
|
+
between deck sections).
|
|
1719
|
+
|
|
1720
|
+
cover
|
|
1721
|
+
Opening slide of a deck. Once per deck. Sets the tone before
|
|
1722
|
+
anything else.
|
|
1723
|
+
|
|
1724
|
+
title-body
|
|
1725
|
+
The workhorse for content slides - 60-70% of slides in any
|
|
1726
|
+
real deck. Title at top in subtitle role (40px) so an action
|
|
1727
|
+
title can wrap to two lines without crowding the body.
|
|
1728
|
+
Optional footer slot for source, page number, or context.
|
|
1729
|
+
|
|
1730
|
+
two-column
|
|
1731
|
+
Compare / contrast (before/after, A vs B, problem / solution).
|
|
1732
|
+
A 1-unit gutter splits the columns; optional column headers
|
|
1733
|
+
above each. Bodies anchor top so matched-length content reads
|
|
1734
|
+
as parallel - keep both columns roughly the same length, or
|
|
1735
|
+
switch to title-body and explain the asymmetry in prose.
|
|
1736
|
+
|
|
1737
|
+
three-column
|
|
1738
|
+
Three-way compare. A/B/C variants, before/during/after, three
|
|
1739
|
+
perspectives on the same question. Equal columns separated by
|
|
1740
|
+
a small gutter; optional headers above each. Bias toward
|
|
1741
|
+
keeping all three columns roughly the same length - if one is
|
|
1742
|
+
half-empty, drop it and use two-column instead.
|
|
1743
|
+
|
|
1744
|
+
exhibit
|
|
1745
|
+
Chart on the left (~64% of safe area), takeaway column on the
|
|
1746
|
+
right (~32%), optional source caption underneath, required
|
|
1747
|
+
action title at the top. The chart is the evidence; the
|
|
1748
|
+
takeaway tells the audience what to see. Reserve for business
|
|
1749
|
+
decks where the audience needs a verbal handle on the chart
|
|
1750
|
+
under time pressure. Sibling templates: \`image-and-text\`
|
|
1751
|
+
when image and body should read as balanced peers (54/46,
|
|
1752
|
+
title optional and small); \`figure-hero\` when the chart
|
|
1753
|
+
should fill the slide with no right column at all.
|
|
1754
|
+
|
|
1755
|
+
image-and-text
|
|
1756
|
+
Image on the left (~54% of safe area), supporting body on
|
|
1757
|
+
the right (~43%), balanced. Optional small caption-style
|
|
1758
|
+
title at the top. Use when the image and the body are about
|
|
1759
|
+
equal in weight - "here's a thing + here's what it is".
|
|
1760
|
+
Sibling templates: \`exhibit\` when the chart should
|
|
1761
|
+
dominate and the right column is a narrow set of takeaway
|
|
1762
|
+
bullets; \`figure-hero\` when the image IS the argument
|
|
1763
|
+
(no body column). The image slot accepts markdown image
|
|
1764
|
+
syntax: \`#image: \`.
|
|
1765
|
+
|
|
1766
|
+
figure-hero
|
|
1767
|
+
Image-dominant slide. The figure carries the whole slide
|
|
1768
|
+
and a small caption sits below; no body or takeaway column.
|
|
1769
|
+
The workhorse for research talks and any deck where a chart,
|
|
1770
|
+
screenshot, or photograph is the point. Sibling templates:
|
|
1771
|
+
\`image-and-text\` when you also want a body column;
|
|
1772
|
+
\`exhibit\` when you want a chart plus a narrow takeaway
|
|
1773
|
+
column for a business audience.
|
|
1774
|
+
|
|
1775
|
+
quote
|
|
1776
|
+
Single big idea, customer voice, or a manifesto sentence.
|
|
1777
|
+
Centered both axes so short content sits balanced rather
|
|
1778
|
+
than drifting top-left. Use for sentences, not numbers -
|
|
1779
|
+
reach for \`metric\` when the slide IS a number.
|
|
1780
|
+
|
|
1781
|
+
metric
|
|
1782
|
+
One hero number plus a line of context. \`size=fit\` lets the
|
|
1783
|
+
number scale to its shape; \`maxfont=300px\` is baked in so it
|
|
1784
|
+
can actually feel hero-sized. One short sentence in the
|
|
1785
|
+
\`context\` slot is the rule, not three - more text fights
|
|
1786
|
+
the number for attention and the slide stops feeling like a
|
|
1787
|
+
headline. Use sparingly: one metric slide per deck is the
|
|
1788
|
+
pattern, not three.
|
|
1789
|
+
|
|
1790
|
+
section
|
|
1791
|
+
Section divider between deck parts. The one template that
|
|
1792
|
+
uses a full-bleed background (via \`grid bg=\`) - the contrast
|
|
1793
|
+
against content slides is what signals "we're switching
|
|
1794
|
+
gears" to the audience. Shape fills are still avoided
|
|
1795
|
+
(Consultant-2 rule); only the slide bg is coloured.
|
|
1796
|
+
|
|
1797
|
+
closing
|
|
1798
|
+
Quiet bookend at the end of a deck. Center-aligned, minimal.
|
|
1799
|
+
Don't write "Thanks for listening" or "Questions?" here -
|
|
1800
|
+
both signal "I've run out of content" and the room tunes
|
|
1801
|
+
out. Pick something the audience will remember instead
|
|
1802
|
+
("Start this week, not next", "Boring is the goal", the
|
|
1803
|
+
one number that summarises the deck, etc.). The contact
|
|
1804
|
+
slot is for one short line of channels, not a paragraph.
|
|
1805
|
+
|
|
1806
|
+
A user \`@template <name>\` with the same name as a built-in overrides
|
|
1807
|
+
it for the rest of the document, with a warning surfaced on the
|
|
1808
|
+
template's slide. So shipping a custom \`title-body\` is fine - the
|
|
1809
|
+
resolver just lets you know the stdlib version got shadowed.
|
|
1810
|
+
|
|
1811
|
+
\u2500\u2500 ERRORS \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1812
|
+
A slide with parse/render errors shows a red badge at the bottom
|
|
1813
|
+
of the thumbnail listing every problem by line number. The badge
|
|
1814
|
+
has a "Copy" button that puts a diagnostic on the clipboard —
|
|
1815
|
+
errors + the full slide source — for pasting back to an agent.
|
|
1816
|
+
|
|
1817
|
+
Common errors:
|
|
1818
|
+
- "shape extends outside grid WxH" y+h > H (or x+w > W)
|
|
1819
|
+
- \`unknown id "@name"\` @-ref before the shape is declared
|
|
1820
|
+
- \`duplicate id "#name"\` two shapes share an id
|
|
1821
|
+
- \`invalid attribute key\` key must start with a letter
|
|
1822
|
+
|
|
1823
|
+
\u2500\u2500 LIMITATIONS TODAY \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
|
|
1824
|
+
- Templates: no per-consumer attribute overrides; no nested templates.
|
|
1825
|
+
- Arrows draw as straight lines; no routing around other shapes.
|
|
1826
|
+
- No drag/resize edit mode yet; shapes are authored by typing DSL.
|
|
1827
|
+
`;
|
|
1828
|
+
|
|
1829
|
+
const SLIDES_CUSTOM_SHAPES_HELP = `
|
|
1830
|
+
SmallDocs — Slides (raw shapes)
|
|
1831
|
+
===========================
|
|
1832
|
+
Reference + design notes for slides built from raw shapes rather than
|
|
1833
|
+
the stdlib templates. Most decks won't need any of this - if you can
|
|
1834
|
+
express the slide via \`@extends\` on a built-in (cover, title-body,
|
|
1835
|
+
two-column, exhibit, etc.), do that.
|
|
1836
|
+
|
|
1837
|
+
Read DESIGN PRINCIPLES first. The syntax reference below assumes you've
|
|
1838
|
+
made the visual choices the principles describe. Without them, raw
|
|
1839
|
+
shapes consistently produce decks that read as "default PowerPoint"
|
|
1840
|
+
rather than as designed.
|
|
1841
|
+
|
|
1842
|
+
── DESIGN PRINCIPLES ─────────────────────────────────
|
|
1843
|
+
Raw shapes give geometric freedom that templates don't. That
|
|
1844
|
+
freedom is also rope. The notes below are what separate a deck
|
|
1845
|
+
that reads as designed from one that reads as amateur.
|
|
1846
|
+
Internalise them before reaching for the syntax.
|
|
1847
|
+
|
|
1848
|
+
Stroke.
|
|
1849
|
+
Default: NO stroke. Reads modern and confident. The slide
|
|
1850
|
+
background carries the silhouette via fill or whitespace; shapes
|
|
1851
|
+
don't compete with their own outlines.
|
|
1852
|
+
|
|
1853
|
+
Thin neutral stroke (\`strokeWidth\` <= 0.03, \`stroke=#94a3b8\` or
|
|
1854
|
+
a similar cool grey). Reads technical, detail-oriented. Reach
|
|
1855
|
+
for this when several shapes' contours need to be visible AND
|
|
1856
|
+
the fill contrast isn't doing the work on its own. Good for
|
|
1857
|
+
matrices, small multiples, schematic diagrams.
|
|
1858
|
+
|
|
1859
|
+
Thick coloured stroke. AVOID. The strongest tell that a deck
|
|
1860
|
+
wasn't designed. It almost never improves the slide. If a shape
|
|
1861
|
+
needs a thick coloured border to read, the geometry or fill is
|
|
1862
|
+
doing too little work. The only exception is a deliberately
|
|
1863
|
+
drawn arrow or callout where the line itself IS the message;
|
|
1864
|
+
even then, take the colour from the doc accent, not from a
|
|
1865
|
+
third hue.
|
|
1866
|
+
|
|
1867
|
+
Fill.
|
|
1868
|
+
Default: NO fill. Most shapes don't need one - the slide
|
|
1869
|
+
background reads through and the silhouette is implicit.
|
|
1870
|
+
|
|
1871
|
+
Subtle tint (\`#eef2ff\`, \`#f8fafc\`, or similar near-background
|
|
1872
|
+
values). For containers that hold body content - a card around
|
|
1873
|
+
a paragraph, a column header strip. The tint should look like a
|
|
1874
|
+
faint shadow, not a coloured panel.
|
|
1875
|
+
|
|
1876
|
+
Saturated fill. Reserved for the ONE focal element per slide -
|
|
1877
|
+
the navy cell on a segmentation matrix, the SOM tier on a
|
|
1878
|
+
market-sizing diagram, the focal band on a funnel. Saturated
|
|
1879
|
+
fill is your single "look here" gesture. Spend it once.
|
|
1880
|
+
|
|
1881
|
+
Typography-only is not a custom shape.
|
|
1882
|
+
A shape with neither stroke nor fill is invisible - it reads as
|
|
1883
|
+
floating text. If you're not encoding geometry (a position, a
|
|
1884
|
+
size, a relationship, a comparison), you're writing an
|
|
1885
|
+
annotation, and an annotation belongs in a plain text shape or
|
|
1886
|
+
in markdown content. The point of a custom shape is the shape;
|
|
1887
|
+
if it isn't visible, drop it and use a template slot.
|
|
1888
|
+
|
|
1889
|
+
One deviation per slide.
|
|
1890
|
+
Repetition + deviation = recognition. If five shapes share a
|
|
1891
|
+
treatment and the sixth doesn't, the eye lands on the sixth
|
|
1892
|
+
before reading a single label. The deviation IS the slide.
|
|
1893
|
+
Wanting two deviations is usually wanting two slides.
|
|
1894
|
+
|
|
1895
|
+
Shape vocabulary.
|
|
1896
|
+
Limit to two shape primitives per deck - typically a rectangle
|
|
1897
|
+
for cards / containers, plus one polygon (or circle / ellipse)
|
|
1898
|
+
as the variant. Using one primitive consistently across slides
|
|
1899
|
+
builds a visual language the audience learns by slide three.
|
|
1900
|
+
Six different shapes used once each flat-lines that recognition
|
|
1901
|
+
and reads as ornament rather than system.
|
|
1902
|
+
|
|
1903
|
+
Geometry as data.
|
|
1904
|
+
Where a shape's size, position, slope, or area corresponds to a
|
|
1905
|
+
number in the content, make it accurate. SOM at 4.7% of TAM
|
|
1906
|
+
should occupy 4.7% of TAM's area, not 30% because that's what
|
|
1907
|
+
fits the layout. A funnel band's width should be proportional
|
|
1908
|
+
to its population, not chosen for visual balance. Where shapes
|
|
1909
|
+
carry data, geometry IS the argument; labels confirm it.
|
|
1910
|
+
|
|
1911
|
+
When the magnitudes span more than ~50x and a linear scale
|
|
1912
|
+
collapses the tail to a hairline, two honest positions:
|
|
1913
|
+
a) Linear scale. Let the tail be a hairline. The
|
|
1914
|
+
disappearance IS the data (250k next to 28M looks like
|
|
1915
|
+
what 250k actually is next to 28M).
|
|
1916
|
+
b) Square-root or log scale. Readable across the range; state
|
|
1917
|
+
which you used in a caption so the geometry isn't lying.
|
|
1918
|
+
Either is fine. Pick deliberately. Don't fudge a linear scale
|
|
1919
|
+
into "what looks good" - that is lying with shapes.
|
|
1920
|
+
|
|
1921
|
+
Labels outside the shape when the shape is too narrow.
|
|
1922
|
+
A magnitude-proportional shape will sometimes be smaller than
|
|
1923
|
+
its label. Pull the label outside (column-aligned, or with a
|
|
1924
|
+
short leader line) - shrinking the label to fit a hairline
|
|
1925
|
+
shape destroys the only data the shape was carrying. See the
|
|
1926
|
+
TEXT INSIDE NON-RECT SHAPES section for the mechanics.
|
|
1927
|
+
|
|
1928
|
+
Visual rhymes across slides.
|
|
1929
|
+
A shared element that recurs on every custom-shape slide - a
|
|
1930
|
+
horizontal rule at a consistent y, a footer caption pinned to
|
|
1931
|
+
the same line, an accent colour reserved for one role - is
|
|
1932
|
+
what makes a custom-shape deck feel deliberate rather than
|
|
1933
|
+
improvised. Pick one or two such rhymes and hold them across
|
|
1934
|
+
every slide you author.
|
|
1935
|
+
|
|
1936
|
+
Restraint over ornament.
|
|
1937
|
+
The decision to ADD any visual element should require a reason.
|
|
1938
|
+
No fill, no stroke, no extra shape, no second colour is the
|
|
1939
|
+
default. Spend visual weight only on the one or two things the
|
|
1940
|
+
slide is about. Empty space is half the design.
|
|
1941
|
+
|
|
1942
|
+
── SHAPE KINDS ───────────────────────────────────────
|
|
1943
|
+
r x y w h rectangle (x,y = top-left; w,h = size)
|
|
1944
|
+
i x y w h image rect (sugar for \`r\` with \`image=\`; see IMAGES)
|
|
1945
|
+
c cx cy radius circle (cx,cy = center)
|
|
1946
|
+
e cx cy rx ry ellipse (cx,cy = center; rx,ry = half-sizes)
|
|
1947
|
+
l x1 y1 x2 y2 line (decorative, no content)
|
|
1948
|
+
a x1 y1 x2 y2 arrow (decorative, tip lands on (x2,y2);
|
|
1949
|
+
accepts \`^h\` between endpoints to bow)
|
|
1950
|
+
p x1,y1 x2,y2 ... polygon (segment operators between points:
|
|
1951
|
+
~ ^h >P * P1 P2; see below)
|
|
1952
|
+
chev x y w h chevron / arrow-block (x,y = top-left; w,h = bbox
|
|
1953
|
+
including the pointed right tip).
|
|
1954
|
+
Text centres on the BODY (excluding
|
|
1955
|
+
tip), not the bbox.
|
|
1956
|
+
Attrs: tip=N (tip width, default
|
|
1957
|
+
min(h/2, w*0.25)); notch=N (V-shaped
|
|
1958
|
+
left edge for interlocking chevrons,
|
|
1959
|
+
default 0).
|
|
1960
|
+
bub x y w h speech bubble / callout. Rounded rect body at
|
|
1961
|
+
(x,y,w,h); the tail points from the
|
|
1962
|
+
nearest edge to \`tail=tx,ty\` (target
|
|
1963
|
+
point in grid units, REQUIRED).
|
|
1964
|
+
Text centres in the body; the tail
|
|
1965
|
+
does not displace text.
|
|
1966
|
+
Attrs:
|
|
1967
|
+
tail=tx,ty (required)
|
|
1968
|
+
radius=N (corner radius, default 1)
|
|
1969
|
+
tailStyle=concave (default) | sharp | wide
|
|
1970
|
+
concave: sides curve toward tail
|
|
1971
|
+
axis (elegant horn /
|
|
1972
|
+
teardrop, reads as
|
|
1973
|
+
designed)
|
|
1974
|
+
sharp: straight narrow triangle
|
|
1975
|
+
(schematic, "needle")
|
|
1976
|
+
wide: straight broader triangle
|
|
1977
|
+
(friendly, generic
|
|
1978
|
+
callout)
|
|
1979
|
+
cyl x y w h cylinder (database / storage shape). Top and
|
|
1980
|
+
bottom ellipse caps each take \`lip\`
|
|
1981
|
+
height (default ~20% of h, capped
|
|
1982
|
+
by w). Text centres in the body
|
|
1983
|
+
between the caps.
|
|
1984
|
+
Attrs: lip=N (cap height).
|
|
1985
|
+
tab x y w h folder / step-marker tab. Body rectangle with
|
|
1986
|
+
a smaller rectangular tab on top-left
|
|
1987
|
+
joined by a slope. Text centres in
|
|
1988
|
+
the body (below the tab).
|
|
1989
|
+
Attrs: tabW=N (tab width, default
|
|
1990
|
+
~38% of w); tabH=N (tab height,
|
|
1991
|
+
default min(0.22*h, 0.18*w)).
|
|
1992
|
+
doc x y w h document / page with a folded top-right corner.
|
|
1993
|
+
Text centres in the body; the fold
|
|
1994
|
+
triangle is rendered as an 18% black
|
|
1995
|
+
overlay so the corner reads as a 3D
|
|
1996
|
+
fold over any fill colour.
|
|
1997
|
+
Attrs: fold=N (fold size, default
|
|
1998
|
+
15% of min(w, h)).
|
|
1999
|
+
cloud x y w h cloud shape (abstract source / sink for arch
|
|
2000
|
+
diagrams). Single SVG path adapted
|
|
2001
|
+
from a real icon library; no internal
|
|
2002
|
+
seams. Text centres in an inset body
|
|
2003
|
+
(~15% horizontal, 20% vertical) so
|
|
2004
|
+
it doesn't float over the bumps.
|
|
2005
|
+
Attrs: variant=heroicons (default,
|
|
2006
|
+
sharp / modern) | material (puffy /
|
|
2007
|
+
friendly) | bootstrap (smooth /
|
|
2008
|
+
balanced).
|
|
2009
|
+
icon x y w h inline icon from the bundled Lucide library
|
|
2010
|
+
(~1960 icons). Outline-only, no fill
|
|
2011
|
+
by default; stroke colour from
|
|
2012
|
+
\`color=\` attribute.
|
|
2013
|
+
Attrs:
|
|
2014
|
+
name=<icon> (required) - any name
|
|
2015
|
+
from the bundled Lucide
|
|
2016
|
+
set. Run
|
|
2017
|
+
\`sdoc slides icons\` to
|
|
2018
|
+
list everything, or
|
|
2019
|
+
\`sdoc slides icons cloud\`
|
|
2020
|
+
to filter by substring.
|
|
2021
|
+
Common ones: user,
|
|
2022
|
+
database, server, lock,
|
|
2023
|
+
cloud, search,
|
|
2024
|
+
trending-up. Unknown
|
|
2025
|
+
names render a pink
|
|
2026
|
+
struck-through rect
|
|
2027
|
+
placeholder so typos
|
|
2028
|
+
are visible.
|
|
2029
|
+
color=<hex> stroke colour
|
|
2030
|
+
(default #0F1E3A).
|
|
2031
|
+
strokeWidth=N outline width
|
|
2032
|
+
(default 2; Lucide
|
|
2033
|
+
convention).
|
|
2034
|
+
The full icon bundle (~81 KB gzipped)
|
|
2035
|
+
is lazy-loaded on first use, then
|
|
2036
|
+
cached for the session. Until the
|
|
2037
|
+
bundle arrives, a dashed-rect
|
|
2038
|
+
placeholder marks each icon position
|
|
2039
|
+
so the layout doesn't reflow when
|
|
2040
|
+
icons swap in.
|
|
2041
|
+
|
|
2042
|
+
Arrow geometry: the coordinates are the line's centerline. The head is
|
|
2043
|
+
symmetric around the line, extending up to 3 * strokeWidth perpendicular
|
|
2044
|
+
on each side. So a horizontal arrow at y=5.78 with strokeWidth=0.06
|
|
2045
|
+
lines up cleanly with a horizontal line at y=5.78 (same centerline) -
|
|
2046
|
+
no need to offset the arrow's y to "clear" the head. For arrows shorter
|
|
2047
|
+
than 12 * strokeWidth the renderer scales the stroke (and head) down so
|
|
2048
|
+
the tip stays on (x2,y2); the arrow renders thinner than declared, but
|
|
2049
|
+
the endpoints stay honest.
|
|
2050
|
+
|
|
2051
|
+
Polygon segment operators (between adjacent point tokens).
|
|
2052
|
+
All curve operators use through-point semantics: the value you write
|
|
2053
|
+
is a point the curve actually passes through, not a hidden SVG control
|
|
2054
|
+
point. "Place the dot where you want the curve to go" works.
|
|
2055
|
+
|
|
2056
|
+
(none) straight line from previous point
|
|
2057
|
+
~ soft bow with default sagitta = 10% of chord length
|
|
2058
|
+
(shorthand for a gentle ^h; use ^h when you need a
|
|
2059
|
+
specific bow depth)
|
|
2060
|
+
^h arc / bow by sagitta h perpendicular to the chord;
|
|
2061
|
+
h is the actual peak height at t=0.5. Positive h bows
|
|
2062
|
+
to the LEFT of direction-of-travel (for a rightward
|
|
2063
|
+
chord, that is upward)
|
|
2064
|
+
>P quadratic Bezier whose midpoint passes through P
|
|
2065
|
+
(P is \`x,y\` or \`@ref\`; attached: \`>5,3\` / \`>@card.top\`)
|
|
2066
|
+
* P1 P2 cubic Bezier passing through P1 at t=1/3 and P2 at
|
|
2067
|
+
t=2/3 (P1, P2 each \`x,y\` or @ref). For predictable
|
|
2068
|
+
results, keep P1 / P2 within ~20% of chord length
|
|
2069
|
+
perpendicular to the chord; the closed-form cubic
|
|
2070
|
+
through both points uses SVG controls that amplify
|
|
2071
|
+
the offset ~4x, so far-from-chord through-points
|
|
2072
|
+
overshoot dramatically beyond the curve itself.
|
|
2073
|
+
|
|
2074
|
+
Polygon point modifiers (attach to the next point, not to an edge):
|
|
2075
|
+
|
|
2076
|
+
(r round the corner at the next point with radius r.
|
|
2077
|
+
Walks each adjacent edge back by r / tan(half-angle),
|
|
2078
|
+
replaces the sharp vertex with a circular arc tangent
|
|
2079
|
+
to both edges. Only takes effect when both adjacent
|
|
2080
|
+
segments are straight; silently no-ops if either is
|
|
2081
|
+
curved (~, ^, >, *). If r would consume more than half
|
|
2082
|
+
of either neighbouring chord, it shrinks to fit so
|
|
2083
|
+
adjacent rounded corners cannot overlap.
|
|
2084
|
+
|
|
2085
|
+
Example, all four corners of a card softened:
|
|
2086
|
+
p (0.4 0,0 (0.4 8,0 (0.4 8,5 (0.4 0,5 fill=#dbeafe
|
|
2087
|
+
|
|
2088
|
+
The same \`^h\` operator works between an arrow's two endpoints to bow
|
|
2089
|
+
the arrow into a curve:
|
|
2090
|
+
a 2 5 ^0.8 12 5 (rightward arrow bowing upward by 0.8u)
|
|
2091
|
+
a @plan.right ^-0.5 @ship.left (gentle downward bow between two shapes)
|
|
2092
|
+
|
|
2093
|
+
Polygon points are written \`x,y\` (one token per point), not space-
|
|
2094
|
+
separated like \`r x y w h\`. The variable point count needs a delimiter,
|
|
2095
|
+
so a comma is required inside each point.
|
|
2096
|
+
|
|
2097
|
+
All shapes EXCEPT \`l\` and \`a\` can hold markdown after \`|\` - full
|
|
2098
|
+
markdown (headings, lists, bold/italic, code, blockquote, tables).
|
|
2099
|
+
Non-rectangle shapes use their bounding box as the text area (see
|
|
2100
|
+
the TEXT INSIDE NON-RECT SHAPES section).
|
|
2101
|
+
|
|
2102
|
+
No \`fill=\` on a shape -> transparent (slide background shows through).
|
|
2103
|
+
Color values accept any CSS colour: hex (#1e40af), named (tomato),
|
|
2104
|
+
rgb(...), rgba(...).
|
|
2105
|
+
|
|
2106
|
+
Polygon examples:
|
|
2107
|
+
p 50,10 90,50 10,50 | Triangle
|
|
2108
|
+
p 10,10 90,10 ~ 90,50 10,50 | Rounded right edge (the ~ before
|
|
2109
|
+
a point softens that segment)
|
|
2110
|
+
p 2,6 ^0.8 9,6 9,8 2,8 fill=#e9d4a6
|
|
2111
|
+
(loaf-shaped card: arched top, three
|
|
2112
|
+
straight sides. Sagitta 0.8 sets the
|
|
2113
|
+
dome height in grid units)
|
|
2114
|
+
p 1,5 >5,1 9,5 1,8 fill=#dbeafe
|
|
2115
|
+
(one quadratic control point at (5,1)
|
|
2116
|
+
pulls the top edge into a peak)
|
|
2117
|
+
p 0,5 * 4,0 8,10 12,5 fill=#fee2e2
|
|
2118
|
+
(cubic with two controls: classic S-curve
|
|
2119
|
+
signature - rises early, falls late)
|
|
2120
|
+
p 10,20 60,20 60,10 90,30 60,50 60,40 10,40 | Next steps
|
|
2121
|
+
(right-pointing arrow shape - text
|
|
2122
|
+
renders in the polygon's bounding box)
|
|
2123
|
+
|
|
2124
|
+
── COMMON PITFALLS ──────────────────────────────────
|
|
2125
|
+
|
|
2126
|
+
1. \`^h\` is perpendicular to the chord, NOT vertical.
|
|
2127
|
+
|
|
2128
|
+
The docs note that "for a rightward chord, positive bows upward"
|
|
2129
|
+
- true, but for any other chord direction the bow follows the
|
|
2130
|
+
PERPENDICULAR. To dome the top of a polygon across a slanted
|
|
2131
|
+
span, use ONE \`^h\` across the whole top, not two arcs meeting
|
|
2132
|
+
at an apex:
|
|
2133
|
+
|
|
2134
|
+
p 3,7 ^1.8 13,7 13,8 3,8 smooth dome
|
|
2135
|
+
p 3,7 ^1.8 8,2.5 ^-1.8 13,7 13,8 3,8 NOT a dome
|
|
2136
|
+
|
|
2137
|
+
The second form has slanted chords (3,7)->(8,2.5) and
|
|
2138
|
+
(8,2.5)->(13,7). Positive \`h\` bows perpendicular-left of each
|
|
2139
|
+
chord direction, which points AWAY from the would-be apex - the
|
|
2140
|
+
arcs flare outward and meet at a sharp peak, not a smooth dome.
|
|
2141
|
+
|
|
2142
|
+
2. Polygon points use \`x,y\` (one comma-separated token per point).
|
|
2143
|
+
|
|
2144
|
+
Rectangles, lines, and arrows use space-separated coords (\`r x y
|
|
2145
|
+
w h\`, \`a x1 y1 x2 y2\`). Polygons need an in-token delimiter so
|
|
2146
|
+
the parser knows where one point ends and the next begins:
|
|
2147
|
+
|
|
2148
|
+
p 2,7 8,3 13,7 fill=... YES
|
|
2149
|
+
p 2 7 8 3 13 7 fill=... parse error
|
|
2150
|
+
|
|
2151
|
+
The parser flags this with a clear "polygon: points use 'x,y'"
|
|
2152
|
+
error pointing to the offending line.
|
|
2153
|
+
|
|
2154
|
+
3. Shapes don't auto-flow - overlapping bboxes paint over each
|
|
2155
|
+
other.
|
|
2156
|
+
|
|
2157
|
+
Every shape reserves its full (x, y, w, h). Source order is
|
|
2158
|
+
paint order, so a later shape with a fill that overlaps an
|
|
2159
|
+
earlier text shape's bbox covers that text:
|
|
2160
|
+
|
|
2161
|
+
r 6 9 88 5 text=subtitle | Three nested envelopes
|
|
2162
|
+
r 10 12 60 40 fill=#f1f5f9 <- starts at y=12, inside the
|
|
2163
|
+
subtitle bbox (y=9..14);
|
|
2164
|
+
subtitle hidden from y=12 down
|
|
2165
|
+
|
|
2166
|
+
The fix: place the next shape at y + h or later (here, y >= 14).
|
|
2167
|
+
The standard pattern from the gallery is subtitle at \`y=9 h=5\`,
|
|
2168
|
+
so the first content shape goes at y >= 14 (15 is the round
|
|
2169
|
+
number most templates use).
|
|
2170
|
+
|
|
2171
|
+
For \`text=title\` and \`text=subtitle\` add another ~0.6 grid
|
|
2172
|
+
unit because the glyph itself extends below the baseline. A
|
|
2173
|
+
text shape's \`h\` is the layout rect, not a hard clip - letters
|
|
2174
|
+
with descenders (g, p, q, y, j) draw about 20% of the font
|
|
2175
|
+
size below the baseline, which would hang into a perfectly
|
|
2176
|
+
bbox-aligned neighbour:
|
|
2177
|
+
|
|
2178
|
+
r 6 9 88 5 text=subtitle | The team after the Q1 reorg
|
|
2179
|
+
r 40 14 20 8 fill=#1e40af | CEO <- the g in "reorg"
|
|
2180
|
+
still crosses into the
|
|
2181
|
+
top of this box
|
|
2182
|
+
|
|
2183
|
+
Two safe budgets:
|
|
2184
|
+
a) 1 full grid unit of clearance below a text=subtitle row
|
|
2185
|
+
(next shape at y >= 15), or
|
|
2186
|
+
b) at least \`0.2 * fontSize\` in grid units (\`text=subtitle\`
|
|
2187
|
+
is 40px on REF_H=720, so ~0.6 grid units on a 56.25-tall
|
|
2188
|
+
grid).
|
|
2189
|
+
|
|
2190
|
+
The gotcha is loudest under \`text=title\` and \`text=subtitle\`
|
|
2191
|
+
because the font is large; \`text=body\` and \`text=caption\` rarely
|
|
2192
|
+
trip on it.
|
|
2193
|
+
|
|
2194
|
+
Circles and ellipses paint only the inscribed disc, but their
|
|
2195
|
+
bbox still claims the full (cx-r, cy-r, 2r, 2r). A label rect
|
|
2196
|
+
that overlaps the bbox CORNERS (outside the disc) is fine; a
|
|
2197
|
+
label rect that crosses the disc itself gets painted over,
|
|
2198
|
+
even when the label's own bbox doesn't fully overlap the
|
|
2199
|
+
bubble's bbox. The safe rule for bubble + label compositions:
|
|
2200
|
+
place the label's nearest edge at distance >= r + 0.5 grid
|
|
2201
|
+
units from the bubble centre. Common patterns that work:
|
|
2202
|
+
a) label centred BELOW the bubble at y = cy + r + 0.5
|
|
2203
|
+
b) label to the side at x = cx + r + 0.5 (left edge of label)
|
|
2204
|
+
Sizing the label rect to the bubble's bbox - e.g. r 76 18 12 3
|
|
2205
|
+
next to c 84 18 2.4 - puts the text directly under the disc
|
|
2206
|
+
and the bubble fill covers the end of the label.
|
|
2207
|
+
|
|
2208
|
+
4. Code blocks inside shapes look randomly sized across siblings.
|
|
2209
|
+
|
|
2210
|
+
A \`\`\`fenced code block\`\`\` inside a slide shape sizes its OWN
|
|
2211
|
+
font and padding based on its content, not on the surrounding
|
|
2212
|
+
shape. Three sibling shapes with identical \`r ... 14 8\` end
|
|
2213
|
+
up looking different because their code samples differ in
|
|
2214
|
+
line count and line length. The cards then read as a sloppy
|
|
2215
|
+
row instead of a parallel set.
|
|
2216
|
+
|
|
2217
|
+
The fix is discipline at author-time:
|
|
2218
|
+
|
|
2219
|
+
a) Give every code-bearing shape the SAME (w, h). Don't let
|
|
2220
|
+
the longest sample dictate one card's size and the
|
|
2221
|
+
shortest dictate another.
|
|
2222
|
+
b) Set an explicit \`size=Npx\` on every code-bearing shape
|
|
2223
|
+
so the font matches across the row. Auto-fit picks per
|
|
2224
|
+
shape, which is exactly what you don't want for sibling
|
|
2225
|
+
comparison.
|
|
2226
|
+
c) Set an explicit \`padding=N\` on every code-bearing shape
|
|
2227
|
+
to control the inset uniformly.
|
|
2228
|
+
d) Truncate examples with \`...\` rather than letting one
|
|
2229
|
+
card carry six lines and another two. The point of a
|
|
2230
|
+
sibling row is comparison; the longest item sets the
|
|
2231
|
+
shape height for everyone.
|
|
2232
|
+
|
|
2233
|
+
Example - three comparable code cards:
|
|
2234
|
+
|
|
2235
|
+
r 6 35 26 12 fill=#F4F6FA size=14px padding=1 |
|
|
2236
|
+
\`\`\`json
|
|
2237
|
+
["The", " quick", "..."]
|
|
2238
|
+
\`\`\`
|
|
2239
|
+
r 36 35 26 12 fill=#F4F6FA size=14px padding=1 |
|
|
2240
|
+
\`\`\`json
|
|
2241
|
+
[464, 4391, ...]
|
|
2242
|
+
\`\`\`
|
|
2243
|
+
r 66 35 26 12 fill=#F4F6FA size=14px padding=1 |
|
|
2244
|
+
\`\`\`json
|
|
2245
|
+
[[0.12, ...], ...]
|
|
2246
|
+
\`\`\`
|
|
2247
|
+
|
|
2248
|
+
Same w, same h, same size, same padding, truncated to fit.
|
|
2249
|
+
The three cards then read as a parallel set rather than three
|
|
2250
|
+
improvisations.
|
|
2251
|
+
|
|
2252
|
+
── SHAPE ATTRIBUTES ──────────────────────────────────
|
|
2253
|
+
Between geometry and \`|\`:
|
|
2254
|
+
|
|
2255
|
+
Visual:
|
|
2256
|
+
fill=<color> Shape fill
|
|
2257
|
+
stroke=<color> Outline colour
|
|
2258
|
+
strokeWidth=N Outline width (grid units)
|
|
2259
|
+
radius=N Corner radius (rectangles)
|
|
2260
|
+
color=<color> Text colour inside the shape
|
|
2261
|
+
image=<url> Bitmap fill (see IMAGES section)
|
|
2262
|
+
opacity=N 0..1; fades the whole shape (fill + stroke +
|
|
2263
|
+
text overlay) at once. Useful for translucent
|
|
2264
|
+
backdrops, ghosted "previous state" shapes,
|
|
2265
|
+
overlapping highlights. Out-of-range values
|
|
2266
|
+
clamp.
|
|
2267
|
+
|
|
2268
|
+
Numeric attributes (strokeWidth, radius) are in grid units - pick
|
|
2269
|
+
values relative to your grid size, no prescribed defaults. On a
|
|
2270
|
+
100-wide grid, \`radius=2\` is ~2% of slide width.
|
|
2271
|
+
|
|
2272
|
+
For text sizing (text=role, size=, h*Scale=), padding, alignment,
|
|
2273
|
+
and slot ids inside templates, see \`sdoc slides\` - those work the
|
|
2274
|
+
same in raw shapes and template shapes.
|
|
2275
|
+
|
|
2276
|
+
── IDS AND @REFERENCES ──────────────────────────────
|
|
2277
|
+
Declare an id with \`#name\`; reference with \`@name\` or \`@name.anchor\`
|
|
2278
|
+
from line / arrow endpoints. Omit the anchor to default to centre.
|
|
2279
|
+
|
|
2280
|
+
r 10 10 30 20 #title | # Main Point
|
|
2281
|
+
r 60 10 30 20 #detail | Supporting detail
|
|
2282
|
+
|
|
2283
|
+
a @title @detail (default: centres of both shapes)
|
|
2284
|
+
a @title.right @detail.left (explicit: right edge to left edge)
|
|
2285
|
+
|
|
2286
|
+
Each endpoint picks its own anchor independently, so you can connect
|
|
2287
|
+
the bottom of one box to the top of another:
|
|
2288
|
+
l @box-a.bottom @box-b.top
|
|
2289
|
+
|
|
2290
|
+
9 anchors: center (default), top, bottom, left, right, topleft,
|
|
2291
|
+
topright, bottomleft, bottomright.
|
|
2292
|
+
|
|
2293
|
+
Anchors resolve against each shape's BOUNDING BOX. For circles and
|
|
2294
|
+
ellipses that means the circumscribing rectangle, not the perimeter -
|
|
2295
|
+
so @circle.right lands at the box edge, not the curve.
|
|
2296
|
+
|
|
2297
|
+
\`l\` and \`a\` endpoints can mix \`@ref\` with raw \`x y\` coords freely,
|
|
2298
|
+
e.g. \`l @title.bottom 50 30\`.
|
|
2299
|
+
|
|
2300
|
+
Note that \`#name\` is overloaded: in a \`@template\` declaration it
|
|
2301
|
+
marks a slot for consumers to fill; in a raw slide it marks an id
|
|
2302
|
+
for @refs. Same syntax, two purposes - context decides.
|
|
2303
|
+
|
|
2304
|
+
── TEXT INSIDE NON-RECT SHAPES ──────────────────────
|
|
2305
|
+
Text inside a polygon, circle, or ellipse lays out in the shape's
|
|
2306
|
+
AXIS-ALIGNED BOUNDING BOX, not the visible silhouette. For a trapezoid,
|
|
2307
|
+
triangle, or arrow this means the text rectangle is larger than the
|
|
2308
|
+
filled shape - long labels can overhang the slanted edges and end up
|
|
2309
|
+
partly outside the visible polygon.
|
|
2310
|
+
|
|
2311
|
+
Example. A funnel band:
|
|
2312
|
+
p 1,1.8 15,1.8 14.2,3 1.8,3 fill=#e0e7ff align=left | 28M devs
|
|
2313
|
+
The bounding box is x ∈ [1, 15], y ∈ [1.8, 3]. \`align=left\` parks the
|
|
2314
|
+
text at x ≈ 1, which is the leftmost point of the WIDEST corner.
|
|
2315
|
+
At the bottom of the band the silhouette only reaches x ≈ 1.8, so a
|
|
2316
|
+
two-line label would clip on the second line.
|
|
2317
|
+
|
|
2318
|
+
Two ways to handle this:
|
|
2319
|
+
|
|
2320
|
+
1. Short, centered labels.
|
|
2321
|
+
Use \`align=center\` and keep the label short enough that it fits
|
|
2322
|
+
within the silhouette's narrowest waist. Works for symmetric
|
|
2323
|
+
trapezoids, hexagons, ellipses.
|
|
2324
|
+
|
|
2325
|
+
p 5.4,7.4 10.6,7.4 9.4,8.3 6.6,8.3 fill=#312e81 color=#fff align=center |
|
|
2326
|
+
**250k**
|
|
2327
|
+
|
|
2328
|
+
2. Shape-only + separate text-r on top.
|
|
2329
|
+
Draw the polygon WITHOUT content (no \`|\`), then place an \`r\` shape
|
|
2330
|
+
on top, sized to fit safely inside the silhouette. Lets the text
|
|
2331
|
+
box be a clean rectangle while the visible silhouette stays slanted.
|
|
2332
|
+
|
|
2333
|
+
p 1,1.8 15,1.8 14.2,3 1.8,3 fill=#e0e7ff
|
|
2334
|
+
r 2,2 12,0.8 align=center valign=center |
|
|
2335
|
+
**21M** use any AI tool at work
|
|
2336
|
+
|
|
2337
|
+
The \`r\` paints above the \`p\` because it is declared after the \`p\`
|
|
2338
|
+
in source order. Reverse the lines and the polygon would cover the
|
|
2339
|
+
rect instead.
|
|
2340
|
+
|
|
2341
|
+
── POLYGON GEOMETRY ──────────────────────────────────
|
|
2342
|
+
Slant strength. A trapezoid where each side indents 0.8u over a
|
|
2343
|
+
1.2u height (a ~6% slant) reads as a rectangle. If you want the
|
|
2344
|
+
viewer's eye to see "narrowing", make each side indent at least
|
|
2345
|
+
~15% of the band's height. For a stack of bands (funnel, pyramid),
|
|
2346
|
+
pick a constant slant ratio across all bands so the silhouette
|
|
2347
|
+
reads as one continuous taper rather than a hinge between
|
|
2348
|
+
rectangles and triangles.
|
|
2349
|
+
|
|
2350
|
+
Order matters within a slide. Shapes declared later paint over
|
|
2351
|
+
shapes declared earlier (this applies across types — see LAYERING).
|
|
2352
|
+
For nested concentric shapes (TAM/SAM/SOM), declare the outermost
|
|
2353
|
+
first; inner shapes cover the outers' label space. Plan your label
|
|
2354
|
+
positions in the VISIBLE RING between each shape and its inner
|
|
2355
|
+
neighbour - or move labels out to an adjacent \`r\` column.
|
|
2356
|
+
|
|
2357
|
+
Curved segments. Five operators between adjacent points: no operator
|
|
2358
|
+
is a straight segment; \`~\` gives a soft bow at 10% of chord length;
|
|
2359
|
+
\`^h\` arcs / bows the segment by an explicit sagitta h; \`>P\` is a
|
|
2360
|
+
quadratic that passes through P at its midpoint; \`* P1 P2\` is a cubic
|
|
2361
|
+
that passes through P1 and P2 at the curve's third-points. Controls
|
|
2362
|
+
can be \`@refs\`, so the curve docks exactly onto another shape's edge:
|
|
2363
|
+
|
|
2364
|
+
r 1 2 4 3 #card
|
|
2365
|
+
r 11 2 4 3 #note
|
|
2366
|
+
a @card.right ^0.6 @note.left (curved arrow between two cards)
|
|
2367
|
+
|
|
2368
|
+
p @card.bottomleft >@card.bottom @note.bottomleft @note.bottom \\
|
|
2369
|
+
@note.bottomright @card.bottomright fill=#f1f5f9
|
|
2370
|
+
(banded shape: top edge dips between
|
|
2371
|
+
the two cards' bottom centers)
|
|
2372
|
+
|
|
2373
|
+
Bow direction. Positive sagitta bows to the LEFT of direction-of-
|
|
2374
|
+
travel. For a horizontal chord moving right, positive bow = upward.
|
|
2375
|
+
Negative bow flips the curve to the opposite side. The same
|
|
2376
|
+
convention applies to polygon \`^h\` segments and bowed arrows.
|
|
2377
|
+
|
|
2378
|
+
Useful for: rounded card corners, dome / loaf tops, speech-bubble
|
|
2379
|
+
tails, curved connectors, organic silhouettes (leaves, clouds, lenses),
|
|
2380
|
+
S-curve callouts. Bad for text-bearing shapes because the bounding
|
|
2381
|
+
box still treats the curve as if it were a straight chord, so labels
|
|
2382
|
+
may overhang the visible silhouette.
|
|
2383
|
+
|
|
2384
|
+
Concave polygons. The bounding box of a concave shape includes the
|
|
2385
|
+
concavity - text can sit in the notch and overlap a neighbouring
|
|
2386
|
+
shape. For concave shapes (arrows, callouts, chevrons), use the
|
|
2387
|
+
shape-only + r-overlay pattern.
|
|
2388
|
+
|
|
2389
|
+
── COMPOSITE PATTERNS ───────────────────────────────
|
|
2390
|
+
Each pattern obeys the DESIGN PRINCIPLES above: at most one
|
|
2391
|
+
saturated fill per slide (used as the focal element), thin neutral
|
|
2392
|
+
strokes only when contour is doing real work, labels outside the
|
|
2393
|
+
shape when the shape is too narrow to hold them.
|
|
2394
|
+
|
|
2395
|
+
Process flow with a focal step.
|
|
2396
|
+
Two pale frames + one navy focal step. The navy IS the slide's
|
|
2397
|
+
one deviation - it tells the audience which step matters.
|
|
2398
|
+
r 1,3.5 3,1.5 stroke=#cbd5e1 strokeWidth=0.02 align=center valign=center | **Plan**
|
|
2399
|
+
r 4.5,3.5 3,1.5 stroke=#cbd5e1 strokeWidth=0.02 align=center valign=center | **Build**
|
|
2400
|
+
r 8,3.5 3,1.5 fill=#1e40af color=#ffffff align=center valign=center | **Ship**
|
|
2401
|
+
a @plan.right @build.left
|
|
2402
|
+
a @build.right @ship.left
|
|
2403
|
+
|
|
2404
|
+
TAM/SAM/SOM with magnitude-proportional rectangles.
|
|
2405
|
+
All three rects share their top-left corner; sides scale by
|
|
2406
|
+
\`sqrt(value / 32)\` so the AREAS read as the dollar values, not
|
|
2407
|
+
just "three nested shapes". Labels live in the right column with
|
|
2408
|
+
short leaders - the inner rects are too small to hold them and
|
|
2409
|
+
putting labels inside would hide some behind others. Only the
|
|
2410
|
+
focal SOM uses saturated fill.
|
|
2411
|
+
|
|
2412
|
+
# TAM 32 -> sqrt(32/32) = 1.0 (9.0 x 6.0 = 54 sq u)
|
|
2413
|
+
# SAM 8 -> sqrt( 8/32) = 0.5 (4.5 x 3.0 = 13.5)
|
|
2414
|
+
# SOM 1.5 -> sqrt(1.5/32) ~ 0.22 (2.0 x 1.3 ~ 2.6)
|
|
2415
|
+
r 1,2 9 6 stroke=#94a3b8 strokeWidth=0.02
|
|
2416
|
+
r 1,2 4.5 3 stroke=#94a3b8 strokeWidth=0.02
|
|
2417
|
+
r 1,2 2.0 1.3 fill=#1e40af
|
|
2418
|
+
r 11,2 4 6 align=left valign=top |
|
|
2419
|
+
**TAM** $32B - global developer tools
|
|
2420
|
+
**SAM** $8B - AI-coding subset
|
|
2421
|
+
**SOM** $1.5B - CLI-agent slice (4.7% of TAM by area)
|
|
2422
|
+
|
|
2423
|
+
Callout / speech bubble.
|
|
2424
|
+
Polygon for the bubble outline (thin neutral stroke; no fill),
|
|
2425
|
+
\`r\` for the text content positioned to avoid the bubble's tail.
|
|
2426
|
+
Reserved for genuine annotation - if the callout could be a
|
|
2427
|
+
body paragraph, make it one.
|
|
2428
|
+
|
|
2429
|
+
── LAYERING ────────────────────────────────────────
|
|
2430
|
+
Source order = paint order. The shape declared later in the slide
|
|
2431
|
+
paints on top of shapes declared earlier. This holds across shape
|
|
2432
|
+
types — a polygon declared after a rectangle paints above that
|
|
2433
|
+
rectangle and vice versa.
|
|
2434
|
+
|
|
2435
|
+
# Rect first, polygon second - polygon paints on top.
|
|
2436
|
+
r 2 2 10 5 fill=#1e40af
|
|
2437
|
+
p 4,3 12,3 12,6 4,6 fill=#fde68a
|
|
2438
|
+
|
|
2439
|
+
# Reverse the lines, the rect is on top instead.
|
|
2440
|
+
|
|
2441
|
+
That's the whole rule for 95% of decks. If you want a connector
|
|
2442
|
+
arrow above the cards it joins, declare the arrow last. If you want
|
|
2443
|
+
a backdrop behind a card, declare the backdrop first.
|
|
2444
|
+
|
|
2445
|
+
Escape hatch: \`layer=top | mid | bottom\` (default \`mid\`)
|
|
2446
|
+
|
|
2447
|
+
When source order isn't enough — usually inside a template whose
|
|
2448
|
+
consumer adds more shapes — set \`layer=\` to promote or demote a
|
|
2449
|
+
shape across the three coarse sublayers:
|
|
2450
|
+
|
|
2451
|
+
bottom - paints before everything regardless of source position
|
|
2452
|
+
mid - the default
|
|
2453
|
+
top - paints after everything regardless of source position
|
|
2454
|
+
|
|
2455
|
+
Invalid values surface in the error badge. For hand-authored slides
|
|
2456
|
+
you should rarely need \`layer=\` at all; reaching for it is a hint
|
|
2457
|
+
that the shape order itself wants reordering.
|
|
2458
|
+
|
|
2459
|
+
── IMAGES IN SHAPES ────────────────────────────────
|
|
2460
|
+
Any shape (\`r\`, \`c\`, \`p\`, etc.) can hold a bitmap via \`image=<url>\`
|
|
2461
|
+
or the \`i x y w h\` shape sugar (parser-equivalent to \`r\` + \`image=\`).
|
|
2462
|
+
See the IMAGES section in \`sdoc slides\` for the full reference -
|
|
2463
|
+
it works the same in raw shapes and template image slots.
|
|
2464
|
+
|
|
2465
|
+
── WHEN TO STOP AND USE A TEMPLATE ─────────────────
|
|
2466
|
+
If your custom slide ends up being "title at top + body below" or
|
|
2467
|
+
"title + two columns" or "title + chart + takeaway", you're
|
|
2468
|
+
re-implementing a stdlib template. Run \`sdoc slides list\` and pick
|
|
2469
|
+
the closest match - the templates encode safe margins, role
|
|
2470
|
+
typography, and slot semantics that you'd otherwise have to re-derive.
|
|
2471
|
+
|
|
2472
|
+
Raw shapes earn their keep for: market sizing diagrams, custom
|
|
2473
|
+
funnels / pyramids / matrices, decision trees, process flows with
|
|
2474
|
+
arrows between named blocks, anything where the GEOMETRY is the
|
|
2475
|
+
message. For everything else, \`@extends\` first.
|
|
2476
|
+
|
|
2477
|
+
See also:
|
|
2478
|
+
sdoc slides Main slide DSL reference
|
|
2479
|
+
sdoc slides list Built-in templates + slot lists
|
|
2480
|
+
sdoc charts Chart fenced blocks (\`\`\`chart)
|
|
2481
|
+
sdoc diagrams Mermaid fenced blocks (\`\`\`mermaid)
|
|
2482
|
+
`;
|
|
2483
|
+
|
|
2484
|
+
const LIBRARY_HELP = `
|
|
2485
|
+
SmallDocs - Library
|
|
2486
|
+
===============
|
|
2487
|
+
The library is a personal, on-machine index of every markdown file you
|
|
2488
|
+
open with \`sdoc <file>\`. It lets you search, browse, and re-open your
|
|
2489
|
+
notes without remembering where they live on disk.
|
|
2490
|
+
|
|
2491
|
+
WHAT IT IS, IN ONE PARAGRAPH
|
|
2492
|
+
Every \`sdoc <file>\` writes a small record into ~/.sdocs/library-
|
|
2493
|
+
index.json - the file path, its title, its tags, when it was last
|
|
2494
|
+
touched. A small loopback HTTP server reads that index when you visit
|
|
2495
|
+
smalldocs.org/library in a browser, so the page can show your files
|
|
2496
|
+
without anything leaving your machine. Click a result and SmallDocs spins
|
|
2497
|
+
up a live editing session against the file on disk. Nothing about
|
|
2498
|
+
the library ever touches a remote server.
|
|
2499
|
+
|
|
2500
|
+
COMMANDS
|
|
2501
|
+
Open and inspect
|
|
2502
|
+
sdoc library Open the library UI at
|
|
2503
|
+
smalldocs.org/library (or
|
|
2504
|
+
localhost:3000/library when
|
|
2505
|
+
running the dev server).
|
|
2506
|
+
Starts the local agent if it
|
|
2507
|
+
isn't already running.
|
|
2508
|
+
sdoc library ls List markdown indexed under
|
|
2509
|
+
this project (walks up to the
|
|
2510
|
+
nearest .git, falls back to
|
|
2511
|
+
cwd). Pass a path to override
|
|
2512
|
+
scope: \`sdoc library ls ~/notes\`.
|
|
2513
|
+
sdoc library ls --tags Same scope, but prints the tag
|
|
2514
|
+
bag (tag - count, sorted by
|
|
2515
|
+
frequency). Run before tagging
|
|
2516
|
+
a new file to stay consistent.
|
|
2517
|
+
sdoc library status Print enabled/disabled, entry
|
|
2518
|
+
count, last scan time.
|
|
2519
|
+
|
|
2520
|
+
Refresh
|
|
2521
|
+
sdoc library rebuild Walk \$HOME again from scratch
|
|
2522
|
+
and refresh every entry. Use
|
|
2523
|
+
after moving files around or
|
|
2524
|
+
when an entry seems stale.
|
|
2525
|
+
|
|
2526
|
+
On/off
|
|
2527
|
+
sdoc library enable Re-enable indexing-on-open if
|
|
2528
|
+
you had turned it off. New
|
|
2529
|
+
opens will be recorded again.
|
|
2530
|
+
sdoc library disable Stop indexing on \`sdoc <file>\`.
|
|
2531
|
+
The existing index is left in
|
|
2532
|
+
place; the library page still
|
|
2533
|
+
shows what's already there.
|
|
2534
|
+
|
|
2535
|
+
Autostart (macOS)
|
|
2536
|
+
sdoc library autostart Same as \`autostart status\`:
|
|
2537
|
+
print whether the LaunchAgent
|
|
2538
|
+
is installed.
|
|
2539
|
+
sdoc library autostart enable Install the LaunchAgent so the
|
|
2540
|
+
loopback agent comes back on
|
|
2541
|
+
every login. This is the
|
|
2542
|
+
default the first time you run
|
|
2543
|
+
\`sdoc library\`.
|
|
2544
|
+
sdoc library autostart disable Remove the LaunchAgent and
|
|
2545
|
+
record that you turned it off;
|
|
2546
|
+
the default-on logic respects
|
|
2547
|
+
this until you re-enable.
|
|
2548
|
+
sdoc library autostart status Print whether the LaunchAgent
|
|
2549
|
+
is installed and where its
|
|
2550
|
+
plist lives.
|
|
2551
|
+
|
|
2552
|
+
Help
|
|
2553
|
+
sdoc library --help This help.
|
|
2554
|
+
sdoc library help Same.
|
|
2555
|
+
|
|
2556
|
+
WHAT GETS INDEXED
|
|
2557
|
+
Every file you open with \`sdoc <file>\` is recorded at open time.
|
|
2558
|
+
\`sdoc library rebuild\` additionally walks \$HOME looking for
|
|
2559
|
+
markdown that fits these rules:
|
|
2560
|
+
- extension is .md, .mdx, or .markdown
|
|
2561
|
+
- size is at most 1 MB
|
|
2562
|
+
- not under a hidden directory (.git, .venv, .cache, ...)
|
|
2563
|
+
- not under a system / framework directory (node_modules, dist,
|
|
2564
|
+
build, target, vendor, .next, __pycache__, ...)
|
|
2565
|
+
- not under a sensitive directory (.ssh, .aws, .gnupg, .docker,
|
|
2566
|
+
.kube, .gcloud, .azure, .bitwarden, .password-store)
|
|
2567
|
+
- the file's basename does not match the deny list (SSH keys,
|
|
2568
|
+
.env files, .key/.pem/.crt/.kdbx etc., credentials.json,
|
|
2569
|
+
api-secret.yaml, ...). Markdown is deliberately NOT denied by
|
|
2570
|
+
name - \`credentials-handling.md\` is a legitimate note about
|
|
2571
|
+
secrets, not a secret.
|
|
2572
|
+
- the file is not excluded by a .sdocsignore (see below)
|
|
2573
|
+
- the file's front matter does not contain \`sdocs-library: false\`
|
|
2574
|
+
(per-file opt-out)
|
|
2575
|
+
|
|
2576
|
+
PER-FILE OPT-OUT
|
|
2577
|
+
Add \`sdocs-library: false\` to a file's YAML front matter to keep
|
|
2578
|
+
it out of the library. The file still opens with \`sdoc <file>\`,
|
|
2579
|
+
it just isn't indexed.
|
|
2580
|
+
|
|
2581
|
+
---
|
|
2582
|
+
title: My private notes
|
|
2583
|
+
sdocs-library: false
|
|
2584
|
+
---
|
|
2585
|
+
|
|
2586
|
+
PER-DIRECTORY EXCLUDES (.sdocsignore)
|
|
2587
|
+
Drop a \`.sdocsignore\` file into any directory. Subset of gitignore
|
|
2588
|
+
syntax:
|
|
2589
|
+
# comments and blank lines are skipped
|
|
2590
|
+
drafts/ exclude this directory
|
|
2591
|
+
*.local.md exclude by glob, basename match
|
|
2592
|
+
sub/secret.md anchored path match (relative to this file)
|
|
2593
|
+
**/scratch/ cross-directory globstar
|
|
2594
|
+
Patterns inherit downward. Negation (\`!\`) and other gitignore
|
|
2595
|
+
niceties are not in v1.
|
|
2596
|
+
|
|
2597
|
+
EPHEMERAL PATHS AND RESCUE
|
|
2598
|
+
Files opened from OS-managed temp folders are special: the OS will
|
|
2599
|
+
clean them on its own schedule (next reboot, days later, when you
|
|
2600
|
+
empty the trash). The library handles this by taking a one-shot
|
|
2601
|
+
RESCUE COPY at index time and pointing the entry at the copy.
|
|
2602
|
+
|
|
2603
|
+
Rescue triggers when the path sits inside any of:
|
|
2604
|
+
macOS /tmp, /private/tmp, /var/tmp, /private/var/tmp,
|
|
2605
|
+
~/.Trash, plus Node's os.tmpdir()
|
|
2606
|
+
Linux /tmp, /var/tmp, /run, /dev/shm, ~/.cache, plus os.tmpdir()
|
|
2607
|
+
Windows %TEMP%, %TMP%, C:\\Windows\\Temp
|
|
2608
|
+
|
|
2609
|
+
The copy lives at ~/.sdocs/library/rescued/<hash>-<basename>. The
|
|
2610
|
+
hash prefix avoids name collisions between two /tmp/notes.md from
|
|
2611
|
+
different sessions. The entry's \`rescuedFrom\` field records the
|
|
2612
|
+
original path; the agent's security gate accepts requests for both
|
|
2613
|
+
the rescued copy and the original path.
|
|
2614
|
+
|
|
2615
|
+
Caveat: rescue is a SNAPSHOT, not a live mirror. Edit \`/tmp/foo.md\`
|
|
2616
|
+
after the first \`sdoc\` and the rescued copy is stale until you
|
|
2617
|
+
re-open the file (which triggers an index update). Once the OS
|
|
2618
|
+
deletes the original, the rescued copy is what you have.
|
|
2619
|
+
|
|
2620
|
+
TAGGING (TWO WAYS TO TAG)
|
|
2621
|
+
Tags live in the file's YAML front matter on disk. There are two
|
|
2622
|
+
ways to put them there:
|
|
2623
|
+
|
|
2624
|
+
1. Write the front matter directly. The canonical place; survives
|
|
2625
|
+
every export and round-trip.
|
|
2626
|
+
|
|
2627
|
+
---
|
|
2628
|
+
title: Q2 plan
|
|
2629
|
+
tags:
|
|
2630
|
+
- planning
|
|
2631
|
+
- q2
|
|
2632
|
+
- finance
|
|
2633
|
+
---
|
|
2634
|
+
|
|
2635
|
+
The Tags row in the file-info card (browser editor) is also a
|
|
2636
|
+
write into the file's front matter, performed by the Bridge.
|
|
2637
|
+
|
|
2638
|
+
2. CLI flag at open time. Append \`+tag\` to any \`sdoc\` invocation:
|
|
2639
|
+
|
|
2640
|
+
sdoc plan.md +planning +q2
|
|
2641
|
+
|
|
2642
|
+
SmallDocs injects those tags into the file's YAML front matter
|
|
2643
|
+
before the browser receives the content, so they survive future
|
|
2644
|
+
opens, exports, and any sync. The \`+\` prefix is shell-safe
|
|
2645
|
+
(\`#\` would be eaten as a comment). Multiple \`+tag\` args
|
|
2646
|
+
anywhere on the command line are allowed.
|
|
2647
|
+
|
|
2648
|
+
#words in prose are NOT extracted as tags. Front matter is the
|
|
2649
|
+
one source of truth; everything else is a way to write to it.
|
|
2650
|
+
|
|
2651
|
+
EDITING TAGS FROM THE BROWSER
|
|
2652
|
+
Open any indexed file (\`sdoc <file>\`) and look at the file info
|
|
2653
|
+
card above the document. There is a Tags row with chips. Hover a
|
|
2654
|
+
chip for an \`x\` to drop it; click \`+\` at the end of the row to
|
|
2655
|
+
add a new one.
|
|
2656
|
+
|
|
2657
|
+
Edits happen through the BRIDGE (the WebSocket connection that
|
|
2658
|
+
also handles live save). The library agent never writes content -
|
|
2659
|
+
exactly one process writes to your file at a time, which avoids
|
|
2660
|
+
the race condition two parallel writers would create.
|
|
2661
|
+
|
|
2662
|
+
If the Tags row is read-only with a hint, you opened the file via
|
|
2663
|
+
a short link or hash URL and there is no Bridge to save through.
|
|
2664
|
+
Open the file via \`sdoc <file>\` to get an editable Tags row.
|
|
2665
|
+
|
|
2666
|
+
CONSISTENCY HINTS FOR AGENTS
|
|
2667
|
+
Before tagging a new file in a project, run:
|
|
2668
|
+
|
|
2669
|
+
sdoc library ls --tags
|
|
2670
|
+
|
|
2671
|
+
This prints the project's current tag vocabulary (tag - count). Re-use
|
|
2672
|
+
existing tags where possible so the library stays consistent. The
|
|
2673
|
+
scope walks up to the nearest .git, so cd anywhere inside the repo
|
|
2674
|
+
works.
|
|
2675
|
+
|
|
2676
|
+
To see what's already in the project:
|
|
2677
|
+
|
|
2678
|
+
sdoc library ls
|
|
2679
|
+
|
|
2680
|
+
THE LOCAL AGENT
|
|
2681
|
+
The library page (smalldocs.org/library or localhost:3000/library) is
|
|
2682
|
+
hosted from the SmallDocs server; the data it shows comes from a tiny
|
|
2683
|
+
HTTP server the CLI runs at 127.0.0.1:47843. That server:
|
|
2684
|
+
- reads ~/.sdocs/library-index.json
|
|
2685
|
+
- never writes user content (writes go through the Bridge)
|
|
2686
|
+
- refuses any browser request whose Origin is not smalldocs.org,
|
|
2687
|
+
sdocs.dev, or localhost:3000 (extend with SDOCS_URL or
|
|
2688
|
+
SDOCS_AGENT_ALLOWED_ORIGINS)
|
|
2689
|
+
- refuses requests whose Host header is not 127.0.0.1 / localhost
|
|
2690
|
+
(DNS-rebinding guard)
|
|
2691
|
+
- refuses path arguments outside the library, on the deny list,
|
|
2692
|
+
or behind a symlink that resolves outside the library
|
|
2693
|
+
If port 47843 is busy, the CLI falls back to a random port and
|
|
2694
|
+
prints the URL with the port baked in.
|
|
2695
|
+
|
|
2696
|
+
AUTOSTART (macOS)
|
|
2697
|
+
The first time you run \`sdoc library\`, a LaunchAgent plist is
|
|
2698
|
+
written to ~/Library/LaunchAgents so the agent comes back after
|
|
2699
|
+
every reboot. Turn it off explicitly with:
|
|
2700
|
+
|
|
2701
|
+
sdoc library autostart disable
|
|
2702
|
+
|
|
2703
|
+
Once disabled, the default-on logic does not re-enable it without
|
|
2704
|
+
you running \`sdoc library autostart enable\` again. The browser
|
|
2705
|
+
shows a small recovery banner only when autostart was enabled but
|
|
2706
|
+
the agent isn't actually answering.
|
|
2707
|
+
|
|
2708
|
+
Linux and Windows do not have autostart in v1. The agent only
|
|
2709
|
+
runs when you start it explicitly with \`sdoc library\`.
|
|
2710
|
+
|
|
2711
|
+
CLI INTERACTION CHEAT SHEET FOR AGENTS
|
|
2712
|
+
Before authoring a new note inside a project:
|
|
2713
|
+
sdoc library ls --tags see the project's tag vocabulary
|
|
2714
|
+
|
|
2715
|
+
When opening or creating a file with tags:
|
|
2716
|
+
sdoc draft.md +planning +q2 tags get written into front matter
|
|
2717
|
+
|
|
2718
|
+
When you want a file kept OUT of the library:
|
|
2719
|
+
add \`sdocs-library: false\` to its YAML front matter
|
|
2720
|
+
|
|
2721
|
+
When the user asks "where is X?":
|
|
2722
|
+
sdoc library opens the search UI in their browser
|
|
2723
|
+
|
|
2724
|
+
PRIVACY MODEL
|
|
2725
|
+
The library is per-machine and per-user. Nothing about it is
|
|
2726
|
+
uploaded to smalldocs.org or any other server. The browser page on
|
|
2727
|
+
smalldocs.org calls the local agent over plain HTTP on 127.0.0.1; it
|
|
2728
|
+
cannot see file content the agent doesn't already have indexed,
|
|
2729
|
+
and the agent cannot read files outside the library.
|
|
2730
|
+
|
|
2731
|
+
COMMON QUESTIONS
|
|
2732
|
+
Q: I opened a file but I don't see it in the library.
|
|
2733
|
+
A: Check \`sdoc library status\` (is it enabled?), then
|
|
2734
|
+
\`sdoc library rebuild\`. If the file is in front matter
|
|
2735
|
+
\`sdocs-library: false\`, that's the opt-out. Also check for a
|
|
2736
|
+
.sdocsignore in the file's directory or any ancestor.
|
|
2737
|
+
|
|
2738
|
+
Q: The library page shows "agent is not running".
|
|
2739
|
+
A: Run \`sdoc library\` once to start it, or enable autostart with
|
|
2740
|
+
\`sdoc library autostart enable\`. The banner is dismissible if
|
|
2741
|
+
you don't want one running.
|
|
2742
|
+
|
|
2743
|
+
Q: A tag I added in the browser didn't save.
|
|
2744
|
+
A: The Tags row needs a connected Bridge. Open the file via
|
|
2745
|
+
\`sdoc <file>\` (not via a share URL or short link) so the
|
|
2746
|
+
editor knows the on-disk path.
|
|
2747
|
+
|
|
2748
|
+
Q: I added a tag with \`+tag\` but it didn't appear.
|
|
2749
|
+
A: The \`+\` is what makes it a tag arg; \`tag\` alone would be read
|
|
2750
|
+
as a filename. Valid form: \`+name\` where name starts with a
|
|
2751
|
+
letter and contains letters, digits, underscores, or hyphens
|
|
2752
|
+
(max 64 chars).
|
|
2753
|
+
|
|
2754
|
+
Q: I want to keep work notes out of the library.
|
|
2755
|
+
A: Add \`sdocs-library: false\` to the file's front matter, or
|
|
2756
|
+
drop a \`.sdocsignore\` into the directory with the pattern.
|
|
2757
|
+
`;
|
|
2758
|
+
|
|
2759
|
+
module.exports = { HELP, COMMENTS_HELP, SCHEMA, CHARTS_HELP, DIAGRAMS_HELP, CELLS_HELP, SLIDES_HELP, SLIDES_CUSTOM_SHAPES_HELP, LIBRARY_HELP };
|