@tinyvolt/pie 0.0.0-stage → 0.2.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.
Files changed (73) hide show
  1. package/.nvmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -2
  4. package/functions/midpoint.ts +29 -0
  5. package/functions/square.ts +20 -0
  6. package/init/index.js +87 -0
  7. package/init/site-agents.md +257 -0
  8. package/init/site-gitignore +28 -0
  9. package/init/site-tsconfig.json +20 -0
  10. package/init/site-vite.config.ts +6 -0
  11. package/layouts/header.tsx +16 -0
  12. package/layouts/index.tsx +10 -0
  13. package/nodes/square.tsx +36 -0
  14. package/package.json +63 -4
  15. package/pages/about.md +5 -0
  16. package/pages/example.md +7 -0
  17. package/pages/python-editor.md +29 -0
  18. package/public/favicon.ico +0 -0
  19. package/public/logo.svg +5 -0
  20. package/site.yml +1 -0
  21. package/src/app.css +387 -0
  22. package/src/app.tsx +30 -0
  23. package/src/authorStyles.ts +3 -0
  24. package/src/components/Header.tsx +16 -0
  25. package/src/components/PageMeta.tsx +40 -0
  26. package/src/components/PageTitle.tsx +10 -0
  27. package/src/components/SplitPane.tsx +170 -0
  28. package/src/components/icons.tsx +94 -0
  29. package/src/entry-client.tsx +4 -0
  30. package/src/entry-server.tsx +21 -0
  31. package/src/global.d.ts +7 -0
  32. package/src/lib/appDir.ts +4 -0
  33. package/src/lib/autodiff/index.ts +49 -0
  34. package/src/lib/layouts/layout.ts +20 -0
  35. package/src/lib/layouts/vitePlugin.ts +39 -0
  36. package/src/lib/markdown/components/CanvasPage.tsx +25 -0
  37. package/src/lib/markdown/components/MarkdownBody.tsx +42 -0
  38. package/src/lib/markdown/components/PythonEditor.tsx +254 -0
  39. package/src/lib/markdown/frontmatter.ts +83 -0
  40. package/src/lib/markdown/pages.ts +23 -0
  41. package/src/lib/markdown/render.ts +62 -0
  42. package/src/lib/markdown/vitePlugin.ts +94 -0
  43. package/src/lib/nodes/components/CenteredGrid.tsx +59 -0
  44. package/src/lib/nodes/components/NodeCanvas.tsx +58 -0
  45. package/src/lib/nodes/execute.ts +166 -0
  46. package/src/lib/nodes/functions/Node2NodeFunc.ts +307 -0
  47. package/src/lib/nodes/functions/array.ts +36 -0
  48. package/src/lib/nodes/functions/autodiff.ts +64 -0
  49. package/src/lib/nodes/functions/basic.ts +298 -0
  50. package/src/lib/nodes/functions/registry.ts +46 -0
  51. package/src/lib/nodes/functions/sideEffects.ts +193 -0
  52. package/src/lib/nodes/functions/vitePlugin.ts +40 -0
  53. package/src/lib/nodes/hooks/useCanvasResize.ts +38 -0
  54. package/src/lib/nodes/jax.ts +191 -0
  55. package/src/lib/nodes/render/renderLogic.tsx +306 -0
  56. package/src/lib/nodes/state/NameGenerator.ts +31 -0
  57. package/src/lib/nodes/state/Node.ts +211 -0
  58. package/src/lib/nodes/state/NodeStore.ts +110 -0
  59. package/src/lib/nodes/tensors/broadcast.ts +101 -0
  60. package/src/lib/nodes/tensors/tensor.ts +42 -0
  61. package/src/lib/python/lift.ts +105 -0
  62. package/src/lib/python/py/__pycache__/pie.cpython-314.pyc +0 -0
  63. package/src/lib/python/py/pie.py +231 -0
  64. package/src/lib/python/py/pie_mpl.py +15 -0
  65. package/src/lib/python/py/show_figures.py +5 -0
  66. package/src/lib/python/runner.ts +202 -0
  67. package/src/lib/python/worker.ts +109 -0
  68. package/src/lib/site/vitePlugin.ts +58 -0
  69. package/src/routes/[...slug].tsx +39 -0
  70. package/src/routes/index.tsx +24 -0
  71. package/src/routes/pages/index.tsx +20 -0
  72. package/styles/theme.css +27 -0
  73. package/vite.ts +96 -0
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "@tinyvolt/pie",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "A Markdown-based SSG for interactive science explainers. Create a site with: npx @tinyvolt/pie init my-site",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/TinyVolt/pie-ssg.git"
9
+ },
10
+ "exports": {
11
+ "./vite": "./vite.ts"
12
+ },
13
+ "bin": {
14
+ "pie": "init/index.js"
15
+ },
16
+ "files": [
17
+ "src",
18
+ "vite.ts",
19
+ "init",
20
+ "pages",
21
+ "functions",
22
+ "nodes",
23
+ "styles",
24
+ "layouts",
25
+ "public",
26
+ "site.yml",
27
+ ".nvmrc"
28
+ ],
29
+ "scripts": {
30
+ "dev": "vite dev",
31
+ "build": "vite build",
32
+ "start": "node .output/server/index.mjs",
33
+ "preview": "vite preview"
34
+ },
35
+ "dependencies": {
36
+ "@codemirror/lang-python": "6.2.1",
37
+ "@codemirror/view": "6.43.14",
38
+ "@jax-js/jax": "0.1.25",
39
+ "@solidjs/meta": "0.29.4",
40
+ "@solidjs/router": "1.0.0",
41
+ "@solidjs/start": "2.0.6",
42
+ "codemirror": "6.0.2",
43
+ "katex": "0.19.0",
44
+ "marked": "18.1.1",
45
+ "nitro": "3.0.260610-beta"
46
+ },
47
+ "peerDependencies": {
48
+ "solid-js": "1.9.14",
49
+ "vite": "8.1.4"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "packageManager": "pnpm@12.9.1",
55
+ "engines": {
56
+ "node": "^24"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "24.19.2",
60
+ "solid-js": "1.9.14",
61
+ "typescript": "7.0.2",
62
+ "vite": "8.1.4"
63
+ },
64
+ "license": "MIT"
65
+ }
package/pages/about.md ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: About
3
+ ---
4
+
5
+ # About
@@ -0,0 +1,7 @@
1
+ ---
2
+ canvas: none
3
+ ---
4
+
5
+ # Example
6
+
7
+ Euler's identity: $e^{i\pi} + 1 = 0$
@@ -0,0 +1,29 @@
1
+ ---
2
+ title: Python editor
3
+ canvas: left
4
+ ---
5
+ # Python editor
6
+
7
+ This is plain python code created using \`\`\`python ... \`\`\` code fence:
8
+ ```python
9
+ A = pie.point(1, 2)
10
+ print(A)
11
+ ```
12
+
13
+ ---
14
+
15
+ Below are python editors created using \`\`\`python-editor ... \`\`\` code fence:
16
+
17
+ ```python-editor collapsed
18
+ a = pie.point(1, 2, out='a')
19
+ r = pie.scalar(1, out='r')
20
+ c = pie.circle(a, r)
21
+ ```
22
+
23
+ ```python-editor
24
+ a = pie.point(0, 0, out='a')
25
+ ```
26
+
27
+ ```python-editor
28
+ pie.scalar(5, out='r')
29
+ ```
Binary file
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 10.5 64 64" width="256" height="256">
2
+ <path d="M32 29 L8 29 A24 24 0 0 0 20 49.78 Z" fill="#41DBC9"/>
3
+ <path d="M32 29 L44 49.78 A24 24 0 0 0 56 29 Z" fill="#41DBC9"/>
4
+ <path d="M32 34 L20 54.78 A24 24 0 0 0 44 54.78 Z" fill="#0B6B73"/>
5
+ </svg>
package/site.yml ADDED
@@ -0,0 +1 @@
1
+ title: pie-ssg
package/src/app.css ADDED
@@ -0,0 +1,387 @@
1
+ body {
2
+ margin: 0;
3
+ color: var(--color-text, #1a1a1a);
4
+ background: var(--color-bg, #fff);
5
+ font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
6
+ line-height: var(--line-height, 1.6);
7
+ }
8
+
9
+ a {
10
+ color: inherit;
11
+ text-underline-offset: 0.15em;
12
+ }
13
+
14
+ #app {
15
+ display: flex;
16
+ flex-direction: column;
17
+ min-height: 100dvh;
18
+ }
19
+
20
+ .site-header {
21
+ display: flex;
22
+ align-items: baseline;
23
+ gap: 1.5rem;
24
+ padding: 0.75rem 1.5rem;
25
+ border-bottom: 1px solid var(--color-border, #ddd);
26
+ }
27
+
28
+ .site-header-title {
29
+ font-weight: 600;
30
+ text-decoration: none;
31
+ }
32
+
33
+ .site-header-nav {
34
+ display: flex;
35
+ gap: 1rem;
36
+ }
37
+
38
+ .site-header-nav a {
39
+ color: var(--color-muted, #555);
40
+ text-decoration: none;
41
+ }
42
+
43
+ .site-header-nav a:hover {
44
+ color: var(--color-hover, #000);
45
+ text-decoration: underline;
46
+ }
47
+
48
+ /* Page text: a centered column about 70 characters wide. */
49
+ main {
50
+ box-sizing: border-box;
51
+ width: 100%;
52
+ max-width: var(--content-width, 40rem);
53
+ margin: 0 auto;
54
+ padding: 2rem 1.5rem 4rem;
55
+ font-family: var(--font-body, Georgia, "Times New Roman", serif);
56
+ font-size: var(--font-size-body, 1.125rem);
57
+ }
58
+
59
+ main h1,
60
+ main h2,
61
+ main h3,
62
+ main h4 {
63
+ line-height: 1.25;
64
+ }
65
+
66
+ main h1 {
67
+ font-size: 2rem;
68
+ margin: 0 0 1rem;
69
+ }
70
+
71
+ main h2 {
72
+ font-size: 1.4rem;
73
+ margin: 2rem 0 0.75rem;
74
+ }
75
+
76
+ main h3 {
77
+ font-size: 1.15rem;
78
+ margin: 1.5rem 0 0.5rem;
79
+ }
80
+
81
+ main p,
82
+ main ul,
83
+ main ol,
84
+ main blockquote,
85
+ main table {
86
+ margin: 0 0 1rem;
87
+ }
88
+
89
+ main li + li {
90
+ margin-top: 0.25rem;
91
+ }
92
+
93
+ main hr {
94
+ margin: 2rem 0;
95
+ border: 0;
96
+ border-top: 1px solid var(--color-border, #ddd);
97
+ }
98
+
99
+ main blockquote {
100
+ padding-left: 1rem;
101
+ border-left: 3px solid var(--color-border, #ddd);
102
+ color: var(--color-muted, #555);
103
+ }
104
+
105
+ main img {
106
+ max-width: 100%;
107
+ }
108
+
109
+ main code {
110
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
111
+ font-size: 0.85em;
112
+ padding: 0.1em 0.3em;
113
+ background: var(--color-code-bg, #f4f4f4);
114
+ border-radius: 3px;
115
+ }
116
+
117
+ main > pre {
118
+ margin: 0 0 1rem;
119
+ padding: 0.75rem 1rem;
120
+ overflow-x: auto;
121
+ background: var(--color-code-bg, #f4f4f4);
122
+ border-radius: 4px;
123
+ line-height: 1.5;
124
+ }
125
+
126
+ main pre code {
127
+ padding: 0;
128
+ background: none;
129
+ }
130
+
131
+ main table {
132
+ border-collapse: collapse;
133
+ }
134
+
135
+ main th,
136
+ main td {
137
+ padding: 0.3rem 0.75rem;
138
+ border-bottom: 1px solid var(--color-border, #ddd);
139
+ text-align: left;
140
+ }
141
+
142
+ main .katex-display {
143
+ overflow-x: auto;
144
+ overflow-y: hidden;
145
+ }
146
+
147
+ .split-pane {
148
+ display: flex;
149
+ min-width: 0;
150
+ min-height: 0;
151
+ overflow: hidden;
152
+ }
153
+
154
+ .split-pane--left {
155
+ flex-direction: row;
156
+ }
157
+
158
+ .split-pane--right {
159
+ flex-direction: row-reverse;
160
+ }
161
+
162
+ .split-pane--top {
163
+ flex-direction: column;
164
+ }
165
+
166
+ .split-pane--bottom {
167
+ flex-direction: column-reverse;
168
+ }
169
+
170
+ /* Each pane scrolls on its own. */
171
+ .split-pane-pane {
172
+ position: relative;
173
+ min-width: 0;
174
+ min-height: 0;
175
+ overflow: auto;
176
+ }
177
+
178
+ /* Draggable divider: a thin bar along whichever axis the split uses. */
179
+ .split-pane-divider {
180
+ position: relative;
181
+ flex: 0 0 6px;
182
+ align-self: stretch;
183
+ background: var(--color-border, #ddd);
184
+ cursor: col-resize;
185
+ z-index: 5;
186
+ transition: background 0.15s ease;
187
+ touch-action: none;
188
+ }
189
+
190
+ .split-pane-divider:hover,
191
+ .split-pane-divider:active {
192
+ background: var(--color-border-hover, #999);
193
+ }
194
+
195
+ .split-pane-divider--horizontal {
196
+ cursor: row-resize;
197
+ }
198
+
199
+ .split-pane-buttons {
200
+ position: absolute;
201
+ top: 50%;
202
+ left: 50%;
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: 0.3rem;
206
+ }
207
+
208
+ .split-pane-divider--horizontal .split-pane-buttons {
209
+ flex-direction: row;
210
+ }
211
+
212
+ .split-pane-button {
213
+ display: inline-flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ width: 1.25rem;
217
+ height: 1.25rem;
218
+ padding: 0;
219
+ background: var(--color-bg, #fff);
220
+ border: 1px solid var(--color-border, #ddd);
221
+ border-radius: 50%;
222
+ color: var(--color-subtle, #666);
223
+ cursor: pointer;
224
+ }
225
+
226
+ .split-pane-button:hover {
227
+ color: var(--color-hover, #000);
228
+ border-color: var(--color-border-hover, #999);
229
+ }
230
+
231
+ /* A canvas page fills the screen below the header. */
232
+ #app:has(.canvas-page) {
233
+ height: 100dvh;
234
+ }
235
+
236
+ .canvas-page {
237
+ flex: 1 1 0;
238
+ }
239
+
240
+ .canvas-page-canvas {
241
+ height: 100%;
242
+ }
243
+
244
+ /* NodeCanvas measures this box to size its grid. */
245
+ .node-canvas {
246
+ width: 100%;
247
+ height: 100%;
248
+ }
249
+
250
+ .canvas-page-canvas svg {
251
+ display: block;
252
+ width: 100%;
253
+ height: 100%;
254
+ }
255
+
256
+ /* Hidden until MarkdownBody swaps it for the editor; keeps its space so the text below doesn't jump. */
257
+ pre:has(> code.language-python-editor) {
258
+ visibility: hidden;
259
+ max-height: 20rem;
260
+ overflow: hidden;
261
+ }
262
+
263
+ .python-editor {
264
+ margin: 1rem 0;
265
+ border: 1px solid var(--color-border, #ddd);
266
+ border-radius: 4px;
267
+ font-family: var(--font-ui, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
268
+ }
269
+
270
+ /* Default cap on the code's height. Dragging the resize bar replaces it with a fixed height. */
271
+ .python-editor .cm-editor {
272
+ font-size: 0.875rem;
273
+ max-height: 20rem;
274
+ }
275
+
276
+ .python-editor .cm-scroller {
277
+ overflow-y: auto;
278
+ }
279
+
280
+ .python-editor-resize {
281
+ height: 6px;
282
+ cursor: row-resize;
283
+ touch-action: none;
284
+ }
285
+
286
+ .python-editor-resize:hover {
287
+ background: var(--color-border, #ddd);
288
+ }
289
+
290
+ .python-editor-toolbar {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 0.5rem;
294
+ padding: 0.25rem 0.5rem;
295
+ border-top: 1px solid var(--color-border, #ddd);
296
+ }
297
+
298
+ .python-editor-button {
299
+ display: inline-flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ width: 1.75rem;
303
+ height: 1.75rem;
304
+ padding: 0;
305
+ background: var(--color-bg, #fff);
306
+ border: 1px solid var(--color-border, #ddd);
307
+ border-radius: 4px;
308
+ cursor: pointer;
309
+ }
310
+
311
+ .python-editor-button:hover {
312
+ border-color: var(--color-border-hover, #999);
313
+ }
314
+
315
+ .python-editor-status {
316
+ color: var(--color-subtle, #666);
317
+ font-size: 0.875rem;
318
+ }
319
+
320
+ .python-editor-result {
321
+ display: inline-flex;
322
+ align-items: center;
323
+ gap: 0.25rem;
324
+ font-size: 0.875rem;
325
+ }
326
+
327
+ .python-editor-result-success {
328
+ color: var(--color-success, #1a7f37);
329
+ }
330
+
331
+ .python-editor-result-error {
332
+ color: var(--color-error, #c00);
333
+ }
334
+
335
+ .python-editor-result-stopped {
336
+ color: var(--color-warning, #9a6700);
337
+ }
338
+
339
+ .python-editor-collapse {
340
+ margin-left: auto;
341
+ }
342
+
343
+ /* Collapsed: only the toolbar shows. Hidden with CSS so the editor keeps its code. */
344
+ .python-editor-collapsed .python-editor-code,
345
+ .python-editor-collapsed .python-editor-resize,
346
+ .python-editor-collapsed .python-editor-output {
347
+ display: none;
348
+ }
349
+
350
+ .python-editor-collapsed .python-editor-toolbar {
351
+ border-top: none;
352
+ }
353
+
354
+ /* Right padding leaves room for the copy button so it doesn't cover the text. */
355
+ .python-editor-output {
356
+ position: relative;
357
+ padding: 0.5rem 2.75rem 0.5rem 0.5rem;
358
+ border-top: 1px solid var(--color-border, #ddd);
359
+ font-size: 0.875rem;
360
+ }
361
+
362
+ .python-editor-copy {
363
+ position: absolute;
364
+ top: 0.5rem;
365
+ right: 0.5rem;
366
+ }
367
+
368
+ .python-editor-text {
369
+ margin: 0;
370
+ max-height: 20rem;
371
+ overflow: auto;
372
+ white-space: pre-wrap;
373
+ }
374
+
375
+ .python-editor-plot {
376
+ display: block;
377
+ max-width: 100%;
378
+ height: auto;
379
+ }
380
+
381
+ .python-editor-error {
382
+ color: var(--color-error, #c00);
383
+ }
384
+
385
+ .python-editor-note {
386
+ color: var(--color-subtle, #666);
387
+ }
package/src/app.tsx ADDED
@@ -0,0 +1,30 @@
1
+ import { MetaProvider } from "@solidjs/meta";
2
+ import { Router } from "@solidjs/router";
3
+ import { FileRoutes } from "@solidjs/start/router";
4
+ import { Suspense } from "solid-js";
5
+ import { Header as BuiltInHeader } from "./components/Header";
6
+ import { PageTitle } from "./components/PageTitle";
7
+ import { layout, type LayoutModules } from "./lib/layouts/layout";
8
+ import "./app.css";
9
+ // Author styles load after `app.css`, so they override it.
10
+ import "./authorStyles";
11
+
12
+ // The site's own `layouts/header.tsx`, or the built-in header.
13
+ const Header = layout(import.meta.glob<LayoutModules[string]>("/layouts/header.tsx", { eager: true }), BuiltInHeader);
14
+
15
+ export default function App() {
16
+ return (
17
+ <Router
18
+ root={props => (
19
+ <MetaProvider>
20
+ {/* Default; a page's own PageTitle overrides it. */}
21
+ <PageTitle />
22
+ <Header />
23
+ <Suspense>{props.children}</Suspense>
24
+ </MetaProvider>
25
+ )}
26
+ >
27
+ <FileRoutes />
28
+ </Router>
29
+ );
30
+ }
@@ -0,0 +1,3 @@
1
+ // Loads every `.css` file in `/styles`. Kept in its own module because Vite hoists
2
+ // glob imports to the top of a file; `app.tsx` imports this after `app.css`.
3
+ import.meta.glob('/styles/**/*.css', { eager: true });
@@ -0,0 +1,16 @@
1
+ import site from "/site.yml";
2
+
3
+ /** The site name and nav links, shown at the top of every page when the site has no `layouts/header.tsx`. */
4
+ export function Header() {
5
+ return (
6
+ <header class="site-header">
7
+ <a class="site-header-title" href="/">
8
+ {site.title}
9
+ </a>
10
+ <nav class="site-header-nav">
11
+ <a href="/about">About</a>
12
+ <a href="/pages">Pages</a>
13
+ </nav>
14
+ </header>
15
+ );
16
+ }
@@ -0,0 +1,40 @@
1
+ import { Link, Meta } from "@solidjs/meta";
2
+ import { useLocation } from "@solidjs/router";
3
+ import { Show } from "solid-js";
4
+ import site from "/site.yml";
5
+ import { PageTitle } from "./PageTitle";
6
+
7
+ /**
8
+ * The tab title plus the tags for search results and link previews. Each route renders it once:
9
+ * `@solidjs/meta` replaces a `<meta>` only when its content matches too, so a page's tag can't override a default.
10
+ * Empty `title` or `description` falls back to `site.yml`. The canonical link and `og:url` need `site.yml`'s `url`.
11
+ */
12
+ export function PageMeta(props: { title?: string; description?: string }) {
13
+ const location = useLocation();
14
+ const ogTitle = () => props.title || site.title;
15
+ const description = () => props.description || site.description;
16
+ return (
17
+ <>
18
+ <PageTitle>{props.title}</PageTitle>
19
+ <Meta property="og:type" content="website" />
20
+ <Meta property="og:site_name" content={site.title} />
21
+ <Meta property="og:title" content={ogTitle()} />
22
+ <Show when={description()}>
23
+ {description => (
24
+ <>
25
+ <Meta name="description" content={description()} />
26
+ <Meta property="og:description" content={description()} />
27
+ </>
28
+ )}
29
+ </Show>
30
+ <Show when={site.url}>
31
+ {url => (
32
+ <>
33
+ <Link rel="canonical" href={url() + location.pathname} />
34
+ <Meta property="og:url" content={url() + location.pathname} />
35
+ </>
36
+ )}
37
+ </Show>
38
+ </>
39
+ );
40
+ }
@@ -0,0 +1,10 @@
1
+ import { Title } from "@solidjs/meta";
2
+ import site from "/site.yml";
3
+
4
+ /** Sets the browser tab title to "<page> – <site title>", or the site title alone when no page title is given. */
5
+ export function PageTitle(props: { children?: string }) {
6
+ // A ternary written inside the JSX gets wrapped in a memo, which `@solidjs/meta` reads
7
+ // outside any owner when it removes the tag, causing a "never be disposed" warning.
8
+ const title = () => (props.children ? `${props.children} – ${site.title}` : site.title);
9
+ return <Title>{title()}</Title>;
10
+ }