@classytic/stage 0.1.0 → 0.3.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/LICENSE +1 -1
- package/README.md +7 -3
- package/dist/assets/index.mjs +0 -1
- package/dist/assets/kit/glyphs.d.mts +5 -5
- package/dist/assets/kit/glyphs.mjs +4 -4
- package/dist/builder/Palette.mjs +50 -89
- package/dist/builder/SceneBuilder.mjs +18 -76
- package/dist/builder/editor.d.mts +2 -1
- package/dist/builder/editor.mjs +14 -4
- package/dist/builder/tools.d.mts +1 -1
- package/dist/builder/tools.mjs +1 -1
- package/dist/chem/index.d.mts +5 -5
- package/dist/chem/index.mjs +4 -4
- package/dist/circuit/index.d.mts +92 -0
- package/dist/circuit/index.mjs +333 -0
- package/dist/core/clock.d.mts +1 -1
- package/dist/core/clock.mjs +3 -3
- package/dist/core/control.d.mts +1 -1
- package/dist/core/control.mjs +2 -2
- package/dist/core/coords.d.mts +4 -4
- package/dist/core/coords.mjs +1 -1
- package/dist/core/index.d.mts +2 -1
- package/dist/core/index.mjs +2 -1
- package/dist/core/learner.d.mts +1 -1
- package/dist/core/learner.mjs +1 -1
- package/dist/core/math.d.mts +26 -0
- package/dist/core/math.mjs +37 -0
- package/dist/core/motion.d.mts +6 -6
- package/dist/core/motion.mjs +6 -6
- package/dist/core/richText.d.mts +2 -2
- package/dist/core/vec.d.mts +1 -1
- package/dist/field/index.d.mts +11 -3
- package/dist/field/index.mjs +20 -3
- package/dist/finance/bizsim.d.mts +93 -0
- package/dist/finance/bizsim.mjs +117 -0
- package/dist/finance/index.d.mts +118 -0
- package/dist/finance/index.mjs +203 -0
- package/dist/index.d.mts +6 -3
- package/dist/index.mjs +6 -4
- package/dist/interaction/MovableDot.d.mts +17 -1
- package/dist/interaction/MovableDot.mjs +160 -43
- package/dist/interaction/index.d.mts +2 -1
- package/dist/interaction/index.mjs +2 -1
- package/dist/interaction/useDraggable.d.mts +1 -1
- package/dist/interaction/useDraggable.mjs +55 -5
- package/dist/interaction/usePressSpring.d.mts +24 -0
- package/dist/interaction/usePressSpring.mjs +67 -0
- package/dist/logic/ast.d.mts +1 -1
- package/dist/logic/index.mjs +8 -3
- package/dist/logic/minimize.d.mts +2 -2
- package/dist/logic/minimize.mjs +56 -18
- package/dist/logic/table.mjs +1 -1
- package/dist/math/ast.d.mts +1 -1
- package/dist/math/ast.mjs +1 -1
- package/dist/math/calculus.d.mts +22 -1
- package/dist/math/calculus.mjs +224 -11
- package/dist/math/compile.d.mts +8 -0
- package/dist/math/compile.mjs +40 -0
- package/dist/math/defs.mjs +1 -1
- package/dist/math/index.d.mts +5 -4
- package/dist/math/index.mjs +5 -4
- package/dist/math/latex.mjs +8 -0
- package/dist/math/parse.mjs +2 -2
- package/dist/math/tokenize.mjs +1 -1
- package/dist/primitives/CanvasLayer.mjs +3 -3
- package/dist/primitives/Dot.d.mts +2 -15
- package/dist/primitives/Dot.mjs +6 -4
- package/dist/primitives/Grid.d.mts +41 -14
- package/dist/primitives/Grid.mjs +100 -22
- package/dist/primitives/Label.d.mts +2 -15
- package/dist/primitives/Label.mjs +4 -3
- package/dist/primitives/Lines.d.mts +4 -32
- package/dist/primitives/Lines.mjs +10 -8
- package/dist/primitives/Shapes.d.mts +5 -43
- package/dist/primitives/Shapes.mjs +12 -10
- package/dist/primitives/Tex.mjs +2 -2
- package/dist/primitives/index.d.mts +2 -2
- package/dist/primitives/index.mjs +2 -2
- package/dist/primitives/props.mjs +31 -0
- package/dist/scene/Scene.d.mts +6 -1
- package/dist/scene/Scene.mjs +16 -41
- package/dist/scene/assets.d.mts +2 -2
- package/dist/scene/assets.mjs +2 -2
- package/dist/scene/commands.d.mts +10 -1
- package/dist/scene/commands.mjs +42 -2
- package/dist/scene/evaluators.mjs +2 -2
- package/dist/scene/index.d.mts +2 -2
- package/dist/scene/index.mjs +2 -2
- package/dist/scene/migrate.mjs +1 -1
- package/dist/scene/sims.mjs +1 -1
- package/dist/scene/types.d.mts +4 -4
- package/dist/sim/particles.d.mts +2 -2
- package/dist/sim/rate.d.mts +4 -4
- package/dist/sim/rate.mjs +1 -1
- package/dist/sim/registry.d.mts +1 -1
- package/dist/sim/sampler.d.mts +5 -5
- package/dist/sim/thermal.d.mts +3 -3
- package/dist/sim/thermal.mjs +2 -2
- package/dist/sim/types.d.mts +2 -2
- package/dist/sim/wave.d.mts +2 -2
- package/dist/sim/wave.mjs +2 -2
- package/dist/steps/index.d.mts +1 -1
- package/dist/steps/index.mjs +3 -3
- package/dist/thermo/index.d.mts +1 -1
- package/dist/thermo/index.mjs +1 -1
- package/dist/view/Stage.mjs +6 -13
- package/dist/view/useInView.mjs +1 -1
- package/package.json +37 -25
- package/styles.css +141 -1
- package/dist/assets/kit/index.mjs +0 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Domain‑neutral visual primitive engine for the web.** Declarative SVG diagram/math
|
|
4
4
|
primitives, draggable handles, a math↔pixel coordinate system, theming, a parametric
|
|
5
5
|
asset/glyph kit, a serializable `SceneDoc` graph (resolver + command/undo), and a
|
|
6
|
-
|
|
6
|
+
stepped‑lesson sequence engine (`StepSpec`). Reactive, web‑native, SSR‑safe, and agent‑drivable — the
|
|
7
7
|
render foundation under [`@classytic/labs`](https://github.com/classytic/labs).
|
|
8
8
|
|
|
9
9
|
```bash
|
|
@@ -14,7 +14,7 @@ npm i @classytic/stage react react-dom
|
|
|
14
14
|
|
|
15
15
|
Everything is also re‑exported from the root for convenience, but prefer subpaths so
|
|
16
16
|
your bundle pulls only what it uses (ESM, per‑file emit; `sideEffects` are scoped to
|
|
17
|
-
`**/*.css
|
|
17
|
+
`**/*.css`, so JavaScript tree-shakes while the shared stylesheet is preserved):
|
|
18
18
|
|
|
19
19
|
| Subpath | What's in it |
|
|
20
20
|
|---|---|
|
|
@@ -27,7 +27,7 @@ your bundle pulls only what it uses (ESM, per‑file emit; `sideEffects` are sco
|
|
|
27
27
|
| `@classytic/stage/builder` | `Editor`, `SceneBuilder`, `Palette`, construction `*_TOOLS` |
|
|
28
28
|
| `@classytic/stage/assets` | `StageAssetDefs` + the glyph kit (`XBlockGlyph`, `WeightGlyph`, `BucketGlyph`, `HangerHook`) |
|
|
29
29
|
| `@classytic/stage/math` | dependency‑free expression engine (`compileExpr`, `differentiate`, `toLatex`…) |
|
|
30
|
-
| `@classytic/stage/
|
|
30
|
+
| `@classytic/stage/steps` | `StepProgressProvider`, `useStepProgress`, `StepScope` (step sequence + gating + progress) |
|
|
31
31
|
| `@classytic/stage/styles.css` | theme tokens + control‑kit CSS (import once in global CSS) |
|
|
32
32
|
|
|
33
33
|
## Quick start
|
|
@@ -63,3 +63,7 @@ export function Demo() {
|
|
|
63
63
|
## License
|
|
64
64
|
|
|
65
65
|
MIT
|
|
66
|
+
|
|
67
|
+
## Trademark
|
|
68
|
+
|
|
69
|
+
MIT-licensed code. "Classytic"/"arc" names + logos are trademarks of Classytic LLC — see [TRADEMARK.md](TRADEMARK.md).
|
package/dist/assets/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ interface GlyphBox {
|
|
|
8
8
|
hpx: number;
|
|
9
9
|
label: string;
|
|
10
10
|
}
|
|
11
|
-
/** The UNKNOWN
|
|
11
|
+
/** The UNKNOWN, a crisp rounded tile with an italic label (e.g. "2x"). */
|
|
12
12
|
declare function XBlockGlyph({
|
|
13
13
|
cx,
|
|
14
14
|
top,
|
|
@@ -16,7 +16,7 @@ declare function XBlockGlyph({
|
|
|
16
16
|
hpx,
|
|
17
17
|
label
|
|
18
18
|
}: GlyphBox): ReactNode;
|
|
19
|
-
/** A container of UNKNOWN amount
|
|
19
|
+
/** A container of UNKNOWN amount, a pail, brand-colored (it IS an unknown, like
|
|
20
20
|
* the tile), labelled "?" until revealed. Built with real form: one light
|
|
21
21
|
* direction (top-left), 2-tone shading for volume (gradient body + a soft
|
|
22
22
|
* right-side shadow + a left specular streak), a rim with visible thickness
|
|
@@ -33,13 +33,13 @@ interface HangerHookProps {
|
|
|
33
33
|
/** Attach point on the beam/arm (pixels). */
|
|
34
34
|
topX: number;
|
|
35
35
|
topY: number;
|
|
36
|
-
/** Attach point on the load
|
|
36
|
+
/** Attach point on the load, the top-centre of the glyph it carries (pixels). */
|
|
37
37
|
botX: number;
|
|
38
38
|
botY: number;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* A suspension: a thin eye on the beam, a taut thread, and an S-hook that grips
|
|
42
|
-
* the load
|
|
42
|
+
* the load, the realistic way a weight hangs from an arm (replaces the old
|
|
43
43
|
* bare "line + dot"). Pure pixel glyph: callers project math→px first. Layered
|
|
44
44
|
* edge/metal/sheen strokes match the beam's metallic read.
|
|
45
45
|
*/
|
|
@@ -49,7 +49,7 @@ declare function HangerHook({
|
|
|
49
49
|
botX,
|
|
50
50
|
botY
|
|
51
51
|
}: HangerHookProps): ReactNode;
|
|
52
|
-
/** A KNOWN value
|
|
52
|
+
/** A KNOWN value, a clean flat coin with the number. */
|
|
53
53
|
declare function WeightGlyph({
|
|
54
54
|
cx,
|
|
55
55
|
top,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/assets/kit/glyphs.tsx
|
|
6
|
-
/** The UNKNOWN
|
|
6
|
+
/** The UNKNOWN, a crisp rounded tile with an italic label (e.g. "2x"). */
|
|
7
7
|
function XBlockGlyph({ cx, top, wpx, hpx, label }) {
|
|
8
8
|
const s = Math.min(wpx, hpx);
|
|
9
9
|
const x0 = cx - s / 2;
|
|
@@ -50,7 +50,7 @@ function XBlockGlyph({ cx, top, wpx, hpx, label }) {
|
|
|
50
50
|
})
|
|
51
51
|
] });
|
|
52
52
|
}
|
|
53
|
-
/** A container of UNKNOWN amount
|
|
53
|
+
/** A container of UNKNOWN amount, a pail, brand-colored (it IS an unknown, like
|
|
54
54
|
* the tile), labelled "?" until revealed. Built with real form: one light
|
|
55
55
|
* direction (top-left), 2-tone shading for volume (gradient body + a soft
|
|
56
56
|
* right-side shadow + a left specular streak), a rim with visible thickness
|
|
@@ -156,7 +156,7 @@ function BucketGlyph({ cx, top, wpx, hpx, label }) {
|
|
|
156
156
|
}
|
|
157
157
|
/**
|
|
158
158
|
* A suspension: a thin eye on the beam, a taut thread, and an S-hook that grips
|
|
159
|
-
* the load
|
|
159
|
+
* the load, the realistic way a weight hangs from an arm (replaces the old
|
|
160
160
|
* bare "line + dot"). Pure pixel glyph: callers project math→px first. Layered
|
|
161
161
|
* edge/metal/sheen strokes match the beam's metallic read.
|
|
162
162
|
*/
|
|
@@ -210,7 +210,7 @@ function HangerHook({ topX, topY, botX, botY }) {
|
|
|
210
210
|
})
|
|
211
211
|
] });
|
|
212
212
|
}
|
|
213
|
-
/** A KNOWN value
|
|
213
|
+
/** A KNOWN value, a clean flat coin with the number. */
|
|
214
214
|
function WeightGlyph({ cx, top, wpx, hpx, label }) {
|
|
215
215
|
const r = Math.min(wpx, hpx) / 2;
|
|
216
216
|
const cy = top + r;
|
package/dist/builder/Palette.mjs
CHANGED
|
@@ -4,96 +4,57 @@ import { ToolIcon } from "./icons.mjs";
|
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
//#region src/builder/Palette.tsx
|
|
7
|
-
const btn = (active) => ({
|
|
8
|
-
display: "inline-flex",
|
|
9
|
-
alignItems: "center",
|
|
10
|
-
gap: 6,
|
|
11
|
-
padding: "6px 10px",
|
|
12
|
-
borderRadius: 8,
|
|
13
|
-
fontSize: 13,
|
|
14
|
-
fontWeight: 600,
|
|
15
|
-
cursor: "pointer",
|
|
16
|
-
color: active ? "var(--stage-bg)" : "var(--stage-fg)",
|
|
17
|
-
background: active ? "var(--stage-accent)" : "transparent",
|
|
18
|
-
border: `1px solid ${active ? "var(--stage-accent)" : "var(--stage-muted)"}`
|
|
19
|
-
});
|
|
20
|
-
const iconBtn = (disabled) => ({
|
|
21
|
-
display: "inline-flex",
|
|
22
|
-
alignItems: "center",
|
|
23
|
-
gap: 6,
|
|
24
|
-
padding: "6px 10px",
|
|
25
|
-
borderRadius: 8,
|
|
26
|
-
fontSize: 13,
|
|
27
|
-
fontWeight: 600,
|
|
28
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
29
|
-
opacity: disabled ? .4 : 1,
|
|
30
|
-
color: "var(--stage-fg)",
|
|
31
|
-
background: "transparent",
|
|
32
|
-
border: "1px solid var(--stage-muted)"
|
|
33
|
-
});
|
|
34
7
|
function Palette({ tools, activeTool, onSelect, onUndo, onRedo, canUndo, canRedo, onClear, onSave, hint }) {
|
|
35
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
children: "Save"
|
|
87
|
-
})
|
|
88
|
-
]
|
|
89
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
90
|
-
style: {
|
|
91
|
-
fontSize: 12,
|
|
92
|
-
opacity: .7,
|
|
93
|
-
margin: "6px 2px 0"
|
|
94
|
-
},
|
|
95
|
-
children: hint
|
|
96
|
-
})] });
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9
|
+
className: "stage-palette",
|
|
10
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
11
|
+
role: "toolbar",
|
|
12
|
+
"aria-label": "construction tools",
|
|
13
|
+
className: "stage-palette__toolbar",
|
|
14
|
+
children: [
|
|
15
|
+
tools.map((t) => /* @__PURE__ */ jsxs("button", {
|
|
16
|
+
type: "button",
|
|
17
|
+
"aria-pressed": activeTool === t.id,
|
|
18
|
+
onClick: () => onSelect(t.id),
|
|
19
|
+
className: "stage-tool-button",
|
|
20
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { name: t.icon }), t.label]
|
|
21
|
+
}, t.id)),
|
|
22
|
+
/* @__PURE__ */ jsx("span", {
|
|
23
|
+
className: "stage-palette__divider",
|
|
24
|
+
"aria-hidden": "true"
|
|
25
|
+
}),
|
|
26
|
+
/* @__PURE__ */ jsxs("button", {
|
|
27
|
+
type: "button",
|
|
28
|
+
onClick: onUndo,
|
|
29
|
+
disabled: !canUndo,
|
|
30
|
+
className: "stage-tool-button",
|
|
31
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { name: "undo" }), "Undo"]
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ jsxs("button", {
|
|
34
|
+
type: "button",
|
|
35
|
+
onClick: onRedo,
|
|
36
|
+
disabled: !canRedo,
|
|
37
|
+
className: "stage-tool-button",
|
|
38
|
+
children: [/* @__PURE__ */ jsx(ToolIcon, { name: "redo" }), "Redo"]
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsx("button", {
|
|
41
|
+
type: "button",
|
|
42
|
+
onClick: onClear,
|
|
43
|
+
className: "stage-tool-button",
|
|
44
|
+
children: "Clear"
|
|
45
|
+
}),
|
|
46
|
+
onSave && /* @__PURE__ */ jsx("button", {
|
|
47
|
+
type: "button",
|
|
48
|
+
onClick: onSave,
|
|
49
|
+
className: "stage-tool-button stage-tool-button--save",
|
|
50
|
+
children: "Save"
|
|
51
|
+
})
|
|
52
|
+
]
|
|
53
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
54
|
+
className: "stage-palette__hint",
|
|
55
|
+
children: hint
|
|
56
|
+
})]
|
|
57
|
+
});
|
|
97
58
|
}
|
|
98
59
|
|
|
99
60
|
//#endregion
|
|
@@ -14,9 +14,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
|
|
15
15
|
//#region src/builder/SceneBuilder.tsx
|
|
16
16
|
/**
|
|
17
|
-
* <SceneBuilder
|
|
17
|
+
* <SceneBuilder>, the click-to-construct authoring surface. Pick a tool, click
|
|
18
18
|
* the canvas: every action funnels through the Editor command stack (undo/redo +
|
|
19
|
-
* the agent API). Output is a SceneDoc (onSave)
|
|
19
|
+
* the agent API). Output is a SceneDoc (onSave), the same JSON the player
|
|
20
20
|
* renders and the DB stores. Free points drag only in Select mode, so clicks in
|
|
21
21
|
* other tools go to the tool, not a drag.
|
|
22
22
|
*/
|
|
@@ -149,12 +149,7 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
149
149
|
}, `pend-${id}`) : null;
|
|
150
150
|
}), [pending, resolved]);
|
|
151
151
|
return /* @__PURE__ */ jsxs("div", {
|
|
152
|
-
className: "not-prose",
|
|
153
|
-
style: {
|
|
154
|
-
display: "flex",
|
|
155
|
-
flexDirection: "column",
|
|
156
|
-
gap: 10
|
|
157
|
-
},
|
|
152
|
+
className: "not-prose stage-builder",
|
|
158
153
|
children: [
|
|
159
154
|
/* @__PURE__ */ jsx(Palette, {
|
|
160
155
|
tools: toolSet,
|
|
@@ -184,21 +179,11 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
184
179
|
]
|
|
185
180
|
}),
|
|
186
181
|
authoring && scalars.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
187
|
-
|
|
188
|
-
display: "flex",
|
|
189
|
-
flexWrap: "wrap",
|
|
190
|
-
gap: 14,
|
|
191
|
-
padding: "2px",
|
|
192
|
-
fontSize: 13
|
|
193
|
-
},
|
|
182
|
+
className: "stage-builder__scalars",
|
|
194
183
|
children: scalars.map((el) => {
|
|
195
184
|
const f = el.free;
|
|
196
185
|
return /* @__PURE__ */ jsxs("label", {
|
|
197
|
-
|
|
198
|
-
display: "inline-flex",
|
|
199
|
-
alignItems: "center",
|
|
200
|
-
gap: 6
|
|
201
|
-
},
|
|
186
|
+
className: "stage-builder__scalar",
|
|
202
187
|
children: [
|
|
203
188
|
/* @__PURE__ */ jsx("strong", { children: el.label ?? el.id }),
|
|
204
189
|
/* @__PURE__ */ jsx("input", {
|
|
@@ -214,10 +199,7 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
214
199
|
})
|
|
215
200
|
}),
|
|
216
201
|
/* @__PURE__ */ jsx("span", {
|
|
217
|
-
|
|
218
|
-
fontVariantNumeric: "tabular-nums",
|
|
219
|
-
minWidth: 24
|
|
220
|
-
},
|
|
202
|
+
className: "stage-builder__scalar-value",
|
|
221
203
|
children: f.value
|
|
222
204
|
})
|
|
223
205
|
]
|
|
@@ -225,30 +207,12 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
225
207
|
})
|
|
226
208
|
}),
|
|
227
209
|
authoring && /* @__PURE__ */ jsxs("details", {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
borderRadius: 8,
|
|
232
|
-
padding: "6px 10px"
|
|
233
|
-
},
|
|
234
|
-
children: [/* @__PURE__ */ jsx("summary", {
|
|
235
|
-
style: {
|
|
236
|
-
cursor: "pointer",
|
|
237
|
-
fontWeight: 600
|
|
238
|
-
},
|
|
239
|
-
children: "Pedagogy — objectives · hints · difficulty"
|
|
240
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
241
|
-
style: {
|
|
242
|
-
display: "grid",
|
|
243
|
-
gap: 8,
|
|
244
|
-
marginTop: 8
|
|
245
|
-
},
|
|
210
|
+
className: "stage-builder__pedagogy",
|
|
211
|
+
children: [/* @__PURE__ */ jsx("summary", { children: "Pedagogy, objectives · hints · difficulty" }), /* @__PURE__ */ jsxs("div", {
|
|
212
|
+
className: "stage-builder__pedagogy-fields",
|
|
246
213
|
children: [
|
|
247
214
|
/* @__PURE__ */ jsxs("label", {
|
|
248
|
-
|
|
249
|
-
display: "grid",
|
|
250
|
-
gap: 3
|
|
251
|
-
},
|
|
215
|
+
className: "stage-builder__field-label",
|
|
252
216
|
children: ["Objectives (one per line)", /* @__PURE__ */ jsx("textarea", {
|
|
253
217
|
rows: 2,
|
|
254
218
|
value: (pedagogy.objectives ?? []).join("\n"),
|
|
@@ -256,14 +220,11 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
256
220
|
...p,
|
|
257
221
|
objectives: splitLines(e.currentTarget.value)
|
|
258
222
|
})),
|
|
259
|
-
|
|
223
|
+
className: "stage-builder__field"
|
|
260
224
|
})]
|
|
261
225
|
}),
|
|
262
226
|
/* @__PURE__ */ jsxs("label", {
|
|
263
|
-
|
|
264
|
-
display: "grid",
|
|
265
|
-
gap: 3
|
|
266
|
-
},
|
|
227
|
+
className: "stage-builder__field-label",
|
|
267
228
|
children: ["Hints (one per line)", /* @__PURE__ */ jsx("textarea", {
|
|
268
229
|
rows: 2,
|
|
269
230
|
value: (pedagogy.hints ?? []).join("\n"),
|
|
@@ -271,42 +232,32 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
271
232
|
...p,
|
|
272
233
|
hints: splitLines(e.currentTarget.value)
|
|
273
234
|
})),
|
|
274
|
-
|
|
235
|
+
className: "stage-builder__field"
|
|
275
236
|
})]
|
|
276
237
|
}),
|
|
277
238
|
/* @__PURE__ */ jsxs("label", {
|
|
278
|
-
|
|
279
|
-
display: "grid",
|
|
280
|
-
gap: 3
|
|
281
|
-
},
|
|
239
|
+
className: "stage-builder__field-label",
|
|
282
240
|
children: ["Success criteria", /* @__PURE__ */ jsx("input", {
|
|
283
241
|
value: pedagogy.successCriteria ?? "",
|
|
284
242
|
onChange: (e) => setPedagogy((p) => ({
|
|
285
243
|
...p,
|
|
286
244
|
successCriteria: e.currentTarget.value || void 0
|
|
287
245
|
})),
|
|
288
|
-
|
|
246
|
+
className: "stage-builder__field"
|
|
289
247
|
})]
|
|
290
248
|
}),
|
|
291
249
|
/* @__PURE__ */ jsxs("label", {
|
|
292
|
-
|
|
293
|
-
display: "inline-flex",
|
|
294
|
-
alignItems: "center",
|
|
295
|
-
gap: 8
|
|
296
|
-
},
|
|
250
|
+
className: "stage-builder__difficulty",
|
|
297
251
|
children: ["Difficulty", /* @__PURE__ */ jsxs("select", {
|
|
298
252
|
value: pedagogy.difficulty ?? "",
|
|
299
253
|
onChange: (e) => setPedagogy((p) => ({
|
|
300
254
|
...p,
|
|
301
255
|
difficulty: e.currentTarget.value ? Number(e.currentTarget.value) : void 0
|
|
302
256
|
})),
|
|
303
|
-
|
|
304
|
-
...FIELD,
|
|
305
|
-
width: "auto"
|
|
306
|
-
},
|
|
257
|
+
className: "stage-builder__field stage-builder__field--select",
|
|
307
258
|
children: [/* @__PURE__ */ jsx("option", {
|
|
308
259
|
value: "",
|
|
309
|
-
children: "
|
|
260
|
+
children: ", "
|
|
310
261
|
}), [
|
|
311
262
|
1,
|
|
312
263
|
2,
|
|
@@ -325,15 +276,6 @@ function SceneBuilder({ initial, onSave, view, height = 440, tools, authoring =
|
|
|
325
276
|
]
|
|
326
277
|
});
|
|
327
278
|
}
|
|
328
|
-
const FIELD = {
|
|
329
|
-
width: "100%",
|
|
330
|
-
border: "1px solid var(--stage-muted)",
|
|
331
|
-
borderRadius: 6,
|
|
332
|
-
padding: "4px 8px",
|
|
333
|
-
background: "transparent",
|
|
334
|
-
color: "inherit",
|
|
335
|
-
font: "inherit"
|
|
336
|
-
};
|
|
337
279
|
const splitLines = (s) => s.split("\n").map((x) => x.trim()).filter(Boolean);
|
|
338
280
|
|
|
339
281
|
//#endregion
|
|
@@ -21,7 +21,8 @@ declare class Editor {
|
|
|
21
21
|
getResolved(): Resolved;
|
|
22
22
|
subscribe(fn: () => void): () => void;
|
|
23
23
|
private emit;
|
|
24
|
-
/** Apply a command. The ONLY mutation entry point.
|
|
24
|
+
/** Apply a command. The ONLY mutation entry point. Validates first: an invalid command (bad
|
|
25
|
+
* id, dangling binding, missing target) is REJECTED with a reason and the doc is untouched. */
|
|
25
26
|
dispatch(cmd: Command, opts?: DispatchOpts): CommandResult;
|
|
26
27
|
/** Group boundary marker (label-only in this impl; batch commands group atomically). */
|
|
27
28
|
mark(_label?: string): void;
|
package/dist/builder/editor.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { resolve } from "../scene/resolve.mjs";
|
|
4
|
-
import { applyCommand, inverse } from "../scene/commands.mjs";
|
|
4
|
+
import { applyCommand, createdIds, inverse, validateCommand } from "../scene/commands.mjs";
|
|
5
5
|
import { useEffect, useReducer, useRef, useState } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/builder/editor.ts
|
|
8
8
|
/**
|
|
9
|
-
* Editor
|
|
9
|
+
* Editor, the single funnel for ALL scene mutations (builder tools, agents,
|
|
10
10
|
* undo/redo, the control-surface bridge). Commands ARE the agent action API:
|
|
11
11
|
* dispatch(cmd) is exactly what a human tool, a voice agent, and an undo all call.
|
|
12
12
|
* Drag is an ephemeral overlay layer; updateDrag writes no command (no undo, no
|
|
@@ -48,8 +48,14 @@ var Editor = class {
|
|
|
48
48
|
emit() {
|
|
49
49
|
this.listeners.forEach((l) => l());
|
|
50
50
|
}
|
|
51
|
-
/** Apply a command. The ONLY mutation entry point.
|
|
51
|
+
/** Apply a command. The ONLY mutation entry point. Validates first: an invalid command (bad
|
|
52
|
+
* id, dangling binding, missing target) is REJECTED with a reason and the doc is untouched. */
|
|
52
53
|
dispatch(cmd, opts = {}) {
|
|
54
|
+
const error = validateCommand(this.doc, cmd);
|
|
55
|
+
if (error) return {
|
|
56
|
+
ok: false,
|
|
57
|
+
error
|
|
58
|
+
};
|
|
53
59
|
const inv = inverse(cmd, this.doc);
|
|
54
60
|
this.doc = applyCommand(this.doc, cmd);
|
|
55
61
|
if (opts.history !== "ignore") {
|
|
@@ -60,7 +66,11 @@ var Editor = class {
|
|
|
60
66
|
this.redoStack = [];
|
|
61
67
|
}
|
|
62
68
|
this.emit();
|
|
63
|
-
|
|
69
|
+
const ids = createdIds(cmd);
|
|
70
|
+
return ids.length ? {
|
|
71
|
+
ok: true,
|
|
72
|
+
createdIds: ids
|
|
73
|
+
} : { ok: true };
|
|
64
74
|
}
|
|
65
75
|
/** Group boundary marker (label-only in this impl; batch commands group atomically). */
|
|
66
76
|
mark(_label) {}
|
package/dist/builder/tools.d.mts
CHANGED
|
@@ -36,7 +36,7 @@ declare const MIDPOINT: Tool;
|
|
|
36
36
|
declare const INTERSECT: Tool;
|
|
37
37
|
declare const MEASURE: Tool;
|
|
38
38
|
declare const DELETE: Tool;
|
|
39
|
-
/** A free scalar parameter
|
|
39
|
+
/** A free scalar parameter, appears as a live slider in the authoring strip. */
|
|
40
40
|
declare const SCALAR: Tool;
|
|
41
41
|
/** A free text note placed in the scene. */
|
|
42
42
|
declare const NOTE: Tool;
|
package/dist/builder/tools.mjs
CHANGED
|
@@ -224,7 +224,7 @@ const DELETE = {
|
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
|
-
/** A free scalar parameter
|
|
227
|
+
/** A free scalar parameter, appears as a live slider in the authoring strip. */
|
|
228
228
|
const SCALAR = {
|
|
229
229
|
id: "scalar",
|
|
230
230
|
icon: "scalar",
|
package/dist/chem/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/chem/index.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* `chem
|
|
3
|
+
* `chem`, acid–base / pH AND electrochemistry / Nernst, as a PURE kernel (no
|
|
4
4
|
* React, no pixels). The engine behind titration curves, pH problems, and galvanic
|
|
5
5
|
* cells.
|
|
6
6
|
*
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* where n_a is the acid initially present, n_b the strong base added, V the total
|
|
13
13
|
* volume. A strong acid is just the K_a → ∞ limit (fully dissociated). Solving the
|
|
14
|
-
* balance (monotonic in h → bisection) is robust everywhere
|
|
15
|
-
* region, equivalence, and past it
|
|
14
|
+
* balance (monotonic in h → bisection) is robust everywhere, initial point, buffer
|
|
15
|
+
* region, equivalence, and past it, with no piecewise approximations to get wrong.
|
|
16
16
|
*
|
|
17
17
|
* Concentrations in mol/L, volumes in litres. Currently models an ACID analyte
|
|
18
18
|
* titrated by a STRONG BASE (covers strong-acid and weak-acid curves).
|
|
@@ -81,7 +81,7 @@ interface CellResult {
|
|
|
81
81
|
declare function galvanicCell(x: HalfCell, y: HalfCell, T?: number): CellResult;
|
|
82
82
|
/** Arrhenius rate constant k = A·e^(−Ea/RT). */
|
|
83
83
|
declare function arrhenius(A: number, Ea: number, T: number): number;
|
|
84
|
-
/** Ratio k(T)/k(Tref) at fixed Ea
|
|
84
|
+
/** Ratio k(T)/k(Tref) at fixed Ea, the stable way to scale a rate without a raw A. */
|
|
85
85
|
declare function arrheniusRatio(Ea: number, T: number, Tref?: number): number;
|
|
86
86
|
/** Fraction of molecules with energy ≥ Ea (Boltzmann) ≈ e^(−Ea/RT). */
|
|
87
87
|
declare function fractionAboveEa(Ea: number, T: number): number;
|
|
@@ -94,7 +94,7 @@ interface StoichSpecies {
|
|
|
94
94
|
name: string;
|
|
95
95
|
/** Stoichiometric coefficient in the balanced equation. */
|
|
96
96
|
coeff: number;
|
|
97
|
-
/** Molar mass, g/mol (optional
|
|
97
|
+
/** Molar mass, g/mol (optional, enables grams in the result). */
|
|
98
98
|
molarMass?: number;
|
|
99
99
|
}
|
|
100
100
|
interface StoichResult {
|
package/dist/chem/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/chem/index.ts
|
|
2
2
|
/**
|
|
3
|
-
* `chem
|
|
3
|
+
* `chem`, acid–base / pH AND electrochemistry / Nernst, as a PURE kernel (no
|
|
4
4
|
* React, no pixels). The engine behind titration curves, pH problems, and galvanic
|
|
5
5
|
* cells.
|
|
6
6
|
*
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* where n_a is the acid initially present, n_b the strong base added, V the total
|
|
13
13
|
* volume. A strong acid is just the K_a → ∞ limit (fully dissociated). Solving the
|
|
14
|
-
* balance (monotonic in h → bisection) is robust everywhere
|
|
15
|
-
* region, equivalence, and past it
|
|
14
|
+
* balance (monotonic in h → bisection) is robust everywhere, initial point, buffer
|
|
15
|
+
* region, equivalence, and past it, with no piecewise approximations to get wrong.
|
|
16
16
|
*
|
|
17
17
|
* Concentrations in mol/L, volumes in litres. Currently models an ACID analyte
|
|
18
18
|
* titrated by a STRONG BASE (covers strong-acid and weak-acid curves).
|
|
@@ -90,7 +90,7 @@ function galvanicCell(x, y, T = T_STD) {
|
|
|
90
90
|
function arrhenius(A, Ea, T) {
|
|
91
91
|
return A * Math.exp(-Ea / (R * T));
|
|
92
92
|
}
|
|
93
|
-
/** Ratio k(T)/k(Tref) at fixed Ea
|
|
93
|
+
/** Ratio k(T)/k(Tref) at fixed Ea, the stable way to scale a rate without a raw A. */
|
|
94
94
|
function arrheniusRatio(Ea, T, Tref = T_STD) {
|
|
95
95
|
return Math.exp(-(Ea / R) * (1 / T - 1 / Tref));
|
|
96
96
|
}
|