@scenoco-three/editor 0.5.4 → 0.6.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 (113) hide show
  1. package/README.md +31 -93
  2. package/dist/cli/index.js +49 -115
  3. package/dist-app/assets/EffectComposer-lekj9UUq.js +1 -0
  4. package/dist-app/assets/GLTFLoader-C2lUhzim.js +1 -0
  5. package/dist-app/assets/OutputPass-DZtd342R.js +72 -0
  6. package/dist-app/assets/Pass-BP3b3hlf.js +1 -0
  7. package/dist-app/assets/RenderPass-BjVchlYg.js +1 -0
  8. package/dist-app/assets/ShaderPass-DMXLcV9X.js +24 -0
  9. package/dist-app/assets/SkeletonUtils-Cxy_owx9.js +1 -0
  10. package/dist-app/assets/index-AxOpglZ-.css +1 -0
  11. package/dist-app/assets/index-D-Iq7tix.js +4135 -0
  12. package/dist-app/assets/inter-CaiW00F8.js +1 -0
  13. package/dist-app/assets/registry-BOM35tSr.js +1433 -0
  14. package/dist-app/assets/worker-_Lh8Vt-i.js +1270 -0
  15. package/dist-app/examples/breakout/assets/geometry/ball.geometry.xml +1 -0
  16. package/dist-app/examples/breakout/assets/geometry/brick.geometry.xml +1 -0
  17. package/dist-app/examples/breakout/assets/geometry/floor-sensor.geometry.xml +1 -0
  18. package/dist-app/examples/breakout/assets/geometry/paddle.geometry.xml +1 -0
  19. package/dist-app/examples/breakout/assets/geometry/wall-side.geometry.xml +1 -0
  20. package/dist-app/examples/breakout/assets/geometry/wall-top.geometry.xml +1 -0
  21. package/dist-app/examples/breakout/assets/materials/ball.material.xml +1 -0
  22. package/dist-app/examples/breakout/assets/materials/brick-firm.material.xml +1 -0
  23. package/dist-app/examples/breakout/assets/materials/brick-hard.material.xml +1 -0
  24. package/dist-app/examples/breakout/assets/materials/brick-soft.material.xml +1 -0
  25. package/dist-app/examples/breakout/assets/materials/paddle.material.xml +10 -0
  26. package/dist-app/examples/breakout/assets/materials/wall.material.xml +1 -0
  27. package/dist-app/examples/breakout/assets/render/neon.render.xml +14 -0
  28. package/dist-app/examples/breakout/assets/scenes/arena.prefab.xml +68 -0
  29. package/dist-app/examples/breakout/assets/scenes/brick.prefab.xml +15 -0
  30. package/dist-app/examples/breakout/assets/scenes/hud.scene.xml +56 -0
  31. package/dist-app/examples/breakout/assets/scenes/level-1.scene.xml +36 -0
  32. package/dist-app/examples/breakout/assets/scenes/level-2.scene.xml +36 -0
  33. package/dist-app/examples/breakout/assets/scenes/level-3.scene.xml +36 -0
  34. package/dist-app/examples/castle/assets/geometry/capsule.geometry.xml +3 -0
  35. package/dist-app/examples/castle/assets/geometry/cone.geometry.xml +2 -0
  36. package/dist-app/examples/castle/assets/geometry/cube.geometry.xml +3 -0
  37. package/dist-app/examples/castle/assets/geometry/cylinder.geometry.xml +3 -0
  38. package/dist-app/examples/castle/assets/geometry/plane.geometry.xml +4 -0
  39. package/dist-app/examples/castle/assets/geometry/quad.geometry.xml +3 -0
  40. package/dist-app/examples/castle/assets/geometry/sphere.geometry.xml +2 -0
  41. package/dist-app/examples/castle/assets/materials/grass.material.xml +1 -0
  42. package/dist-app/examples/castle/assets/materials/roof.material.xml +1 -0
  43. package/dist-app/examples/castle/assets/materials/stone.material.xml +1 -0
  44. package/dist-app/examples/castle/assets/materials/wood.material.xml +1 -0
  45. package/dist-app/examples/castle/assets/scenes/main.scene.xml +25 -0
  46. package/dist-app/examples/collector/assets/geometry/capsule.geometry.xml +3 -0
  47. package/dist-app/examples/collector/assets/geometry/cone.geometry.xml +2 -0
  48. package/dist-app/examples/collector/assets/geometry/cube.geometry.xml +3 -0
  49. package/dist-app/examples/collector/assets/geometry/cylinder.geometry.xml +3 -0
  50. package/dist-app/examples/collector/assets/geometry/plane.geometry.xml +4 -0
  51. package/dist-app/examples/collector/assets/geometry/quad.geometry.xml +3 -0
  52. package/dist-app/examples/collector/assets/geometry/sphere.geometry.xml +2 -0
  53. package/dist-app/examples/collector/assets/materials/crate.material.xml +1 -0
  54. package/dist-app/examples/collector/assets/materials/depot.material.xml +1 -0
  55. package/dist-app/examples/collector/assets/materials/ground.material.xml +1 -0
  56. package/dist-app/examples/collector/assets/materials/player.material.xml +1 -0
  57. package/dist-app/examples/collector/assets/scenes/crate.prefab.xml +9 -0
  58. package/dist-app/examples/collector/assets/scenes/main.scene.xml +83 -0
  59. package/dist-app/examples/playable/assets/geometry/capsule.geometry.xml +3 -0
  60. package/dist-app/examples/playable/assets/geometry/cone.geometry.xml +2 -0
  61. package/dist-app/examples/playable/assets/geometry/cube.geometry.xml +3 -0
  62. package/dist-app/examples/playable/assets/geometry/cylinder.geometry.xml +3 -0
  63. package/dist-app/examples/playable/assets/geometry/plane.geometry.xml +4 -0
  64. package/dist-app/examples/playable/assets/geometry/quad.geometry.xml +3 -0
  65. package/dist-app/examples/playable/assets/geometry/sphere.geometry.xml +2 -0
  66. package/dist-app/examples/playable/assets/materials/ground.material.xml +1 -0
  67. package/dist-app/examples/playable/assets/materials/target.material.xml +2 -0
  68. package/dist-app/examples/playable/assets/scenes/main.scene.xml +53 -0
  69. package/dist-app/examples/playable/assets/scenes/target.prefab.xml +9 -0
  70. package/dist-app/examples.json +68 -0
  71. package/{app → dist-app}/index.html +2 -2
  72. package/dist-app/model.json +1 -0
  73. package/package.json +25 -29
  74. package/app/cameraRig.ts +0 -118
  75. package/app/commands.ts +0 -78
  76. package/app/desktopControls.ts +0 -47
  77. package/app/document.ts +0 -547
  78. package/app/editLog.ts +0 -127
  79. package/app/editor.css +0 -51
  80. package/app/gizmo.ts +0 -169
  81. package/app/globals.d.ts +0 -9
  82. package/app/main.ts +0 -546
  83. package/app/meta.ts +0 -24
  84. package/app/picking.ts +0 -47
  85. package/app/session.ts +0 -612
  86. package/app/touchControls.ts +0 -127
  87. package/app/ui-react/AddMemberPopup.tsx +0 -114
  88. package/app/ui-react/BottomLauncher.tsx +0 -15
  89. package/app/ui-react/EditorUI.tsx +0 -22
  90. package/app/ui-react/FilePicker.tsx +0 -80
  91. package/app/ui-react/HeadBtn.tsx +0 -21
  92. package/app/ui-react/InspectorSheet.tsx +0 -475
  93. package/app/ui-react/NodeListSheet.tsx +0 -56
  94. package/app/ui-react/TopBar.tsx +0 -137
  95. package/app/ui-react/Tree.tsx +0 -106
  96. package/app/ui-react/cx.ts +0 -4
  97. package/app/ui-react/fields.tsx +0 -407
  98. package/app/ui-react/format.ts +0 -30
  99. package/app/ui-react/mount.tsx +0 -31
  100. package/app/ui-react/store.ts +0 -80
  101. package/app/ui-react/types.ts +0 -88
  102. package/app/ui-react/ui.module.css +0 -250
  103. package/app/ui-react/useSize.ts +0 -20
  104. package/app/values.ts +0 -137
  105. package/app/vite-env.d.ts +0 -7
  106. package/dist/index.d.ts +0 -10
  107. package/dist/index.js +0 -10
  108. package/dist/mcp.d.ts +0 -32
  109. package/dist/mcp.js +0 -191
  110. package/dist/protocol.d.ts +0 -170
  111. package/dist/protocol.js +0 -22
  112. package/dist/server.d.ts +0 -124
  113. package/dist/server.js +0 -440
@@ -1,88 +0,0 @@
1
- import type { Object3D, Scene } from 'three';
2
- import type { Engine } from '@scenoco-three/core';
3
- import type { ProjectModel } from '@scenoco-three/compiler';
4
- import type { SceneDocument } from '../document.js';
5
- import type { GizmoMode, GizmoSpace } from '../gizmo.js';
6
-
7
- /**
8
- * The transport/gizmo/edit actions the top bar drives, plus a `state()` snapshot
9
- * it polls to style itself. Structurally identical to what `main.ts` already
10
- * builds — moved here from the old `toolbar.ts` so the React chrome owns the type.
11
- */
12
- export interface ToolbarHandlers {
13
- // transport
14
- play(): void;
15
- pause(): void;
16
- step(): void;
17
- stop(): void;
18
- // gizmo + edit
19
- getMode(): GizmoMode;
20
- setMode(mode: GizmoMode): void;
21
- /** Handles along the world's axes, or the object's own — Unity's Global/Local. */
22
- getSpace(): GizmoSpace;
23
- setSpace(space: GizmoSpace): void;
24
- /** Whether the editor camera is showing the scene's screen-space UI. */
25
- getShowUi(): boolean;
26
- setShowUi(on: boolean): void;
27
- undo(): void;
28
- redo(): void;
29
- save(): void;
30
- /** Current state, re-read on each refresh. */
31
- state(): {
32
- mode: 'edit' | 'play';
33
- playing: boolean;
34
- canUndo: boolean;
35
- canRedo: boolean;
36
- dirty: boolean;
37
- saving: boolean;
38
- /** True when something the document owns is selected — the gizmo has a target. */
39
- enabled: boolean;
40
- /** A short-lived message — a save, or why one was refused. */
41
- status?: string;
42
- };
43
- }
44
-
45
- /**
46
- * Everything the React chrome needs from the host page (`main.ts`): live getters
47
- * for the engine/document, the toolbar handlers, a selection callback (viewport
48
- * highlight + framing), and a "focus the current selection" action.
49
- */
50
- export interface EditorDeps {
51
- getEngine: () => Engine;
52
- getDoc: () => SceneDocument | undefined;
53
- /** What the project's TypeScript declares — the inspector's and the picker's schema. */
54
- getModel: () => ProjectModel | undefined;
55
- /** The live scene, which the editor owns rather than `Engine` (it drives its own loop). */
56
- getScene: () => Scene | null;
57
- handlers: ToolbarHandlers;
58
- /** Called whenever selection changes (from the tree or the viewport) — host highlights/frames it. */
59
- onSelect: (node: Object3D | null) => void;
60
- /** Reframe the camera on a node (the node-list "focus" action). */
61
- focusSelected: (node: Object3D) => void;
62
- /** Reparent a node under a new parent (tree drag) — records an undoable move. */
63
- reparent: (node: Object3D, newParent: Object3D, index?: number) => void;
64
- /** Copy a node's subtree to the clipboard. */
65
- copy: (node: Object3D) => void;
66
- /** Cut (copy + delete) a node's subtree. */
67
- cut: (node: Object3D) => void;
68
- /** Paste the clipboard subtree under `parent` (the scene root when null). */
69
- paste: (parent: Object3D | null) => void;
70
- /** Delete a node's subtree (undoable). */
71
- remove: (node: Object3D) => void;
72
- /** Rename (or set) a node's scene id. */
73
- setId: (node: Object3D, id: string) => void;
74
- /** Whether the clipboard holds a subtree (for Paste's enabled state). */
75
- hasClipboard: () => boolean;
76
- /** Every document in the project — what the open dialog lists. */
77
- listDocuments: () => Promise<Array<{ kind: string; path: string; stem: string }>>;
78
- /** Open one, by project-relative path. Rejects with why, which the dialog shows. */
79
- openDocument: (path: string) => Promise<void>;
80
- }
81
-
82
- /** The tiny imperative surface `main.ts` keeps a handle to (mirrors the old panels/toolbar). */
83
- export interface EditorUIHandle {
84
- /** Re-read the live engine/document into the tree, inspector, and top bar. */
85
- refresh(): void;
86
- /** Select a node from outside (a viewport pick) — syncs tree + inspector. */
87
- select(node: Object3D | null): void;
88
- }
@@ -1,250 +0,0 @@
1
- /* Editor chrome styles (CSS Module — class names are scoped/hashed, referenced as
2
- `styles.x` from the TSX). Shares the dark theme vars from index.html. */
3
-
4
- /* ── top bar ─────────────────────────────────────────────────────────────── */
5
- .topbar {
6
- position: fixed; top: 8px; left: 8px; right: 8px; margin: 0 auto;
7
- width: max-content; max-width: calc(100% - 16px);
8
- display: flex; align-items: center; gap: 2px;
9
- padding: 5px; border-radius: 12px;
10
- background: var(--panel); border: 1px solid var(--edge);
11
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
12
- backdrop-filter: blur(6px); pointer-events: auto;
13
- overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
14
- -webkit-overflow-scrolling: touch;
15
- }
16
- .topbar::-webkit-scrollbar { height: 0; }
17
-
18
- .tbtn {
19
- position: relative; flex: 0 0 auto;
20
- width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
21
- padding: 0; border: 1px solid transparent; border-radius: 9px;
22
- background: transparent; color: #cfe0f2; cursor: pointer;
23
- }
24
- .tbtn svg { width: 21px; height: 21px; display: block; }
25
- .tbtn:hover:not(:disabled) { background: rgba(74, 240, 224, 0.12); color: #eafdff; }
26
- .tbtn.active { background: rgba(74, 240, 224, 0.2); border-color: var(--accent); color: #eafdff; }
27
- .tbtn:disabled { opacity: 0.3; cursor: default; }
28
- .tbtn.save.dirty { color: var(--accent); }
29
- .tbtn.save.dirty::after {
30
- content: ''; position: absolute; top: 7px; right: 7px;
31
- width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
32
- }
33
- .tbtn.save.saving { opacity: 0.5; }
34
- .sep { flex: 0 0 auto; width: 1px; height: 24px; background: var(--line); margin: 0 3px; }
35
-
36
- /* ── bottom launcher ─────────────────────────────────────────────────────── */
37
- .launcher {
38
- position: fixed; left: 12px; bottom: 12px; z-index: 6;
39
- width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;
40
- padding: 0; border-radius: 14px; cursor: pointer; pointer-events: auto;
41
- background: var(--panel); border: 1px solid var(--edge); color: #cfe0f2;
42
- box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
43
- backdrop-filter: blur(6px); box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
44
- }
45
- .launcher:hover { color: #eafdff; border-color: var(--accent); }
46
- .launcher svg { width: 24px; height: 24px; }
47
-
48
- /* ── bottom sheets (node list + inspector) ───────────────────────────────── */
49
- .sheet {
50
- position: fixed; z-index: 7; display: flex; flex-direction: column;
51
- background: var(--panel); border: 1px solid var(--edge);
52
- backdrop-filter: blur(10px); pointer-events: auto;
53
- box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.55);
54
- animation: sheet-in 0.18s ease;
55
- }
56
- @keyframes sheet-in { from { transform: translateY(12px); opacity: 0; } to { transform: none; opacity: 1; } }
57
-
58
- /* Portrait / narrow: a bottom-docked sheet that sits a bit lower (leaves more of
59
- the viewport visible above it); one at a time. */
60
- .sheet {
61
- left: 0; right: 0; bottom: 0;
62
- height: min(50vh, 460px);
63
- border-radius: 16px 16px 0 0;
64
- padding-bottom: env(safe-area-inset-bottom, 0);
65
- }
66
- /* Landscape: full-height side columns (top→bottom) — node list left, inspector
67
- right, both may coexist. */
68
- @media (orientation: landscape) {
69
- .sheet { top: 12px; bottom: 12px; height: auto; border-radius: 16px; width: min(400px, 44vw); }
70
- .sheetNodes { left: 12px; right: auto; }
71
- .sheetInspector { right: 12px; left: auto; }
72
- }
73
-
74
- .head {
75
- flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
76
- padding: 9px 10px 9px 14px; border-bottom: 1px solid var(--line);
77
- }
78
- .title {
79
- flex: 0 1 auto; min-width: 0; font: 700 11px/1 ui-monospace, monospace; color: #9fe7ff;
80
- letter-spacing: 0.08em; text-transform: uppercase;
81
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
82
- }
83
- /* Many actions on a narrow phone: let the row scroll horizontally rather than overflow. */
84
- /* The button group sits against the right edge; `auto` only consumes spare
85
- space, so a crowded header still lets the group shrink and scroll. */
86
- .actions { display: flex; align-items: center; gap: 2px; margin-left: auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
87
- .actions::-webkit-scrollbar { display: none; }
88
- .headBtn {
89
- flex: 0 0 auto;
90
- width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
91
- padding: 0; border: 1px solid transparent; border-radius: 8px;
92
- background: transparent; color: #cfe0f2; cursor: pointer;
93
- }
94
- .headBtn svg { width: 18px; height: 18px; }
95
- .headBtn:hover:not(:disabled) { background: rgba(74, 240, 224, 0.12); color: #eafdff; }
96
- .headBtn:disabled { opacity: 0.32; cursor: default; }
97
- .body { flex: 1 1 auto; overflow: auto; -webkit-overflow-scrolling: touch; }
98
-
99
- /* ── tree (react-arborist rows) ──────────────────────────────────────────── */
100
- .tree { padding: 4px 0; }
101
- .node {
102
- display: flex; align-items: center; gap: 4px; height: 100%;
103
- padding-right: 8px; cursor: pointer; white-space: nowrap;
104
- font: 12px/1.4 ui-monospace, monospace; color: #d6e2f0; border-radius: 6px;
105
- }
106
- .node:hover { background: rgba(74, 240, 224, 0.08); }
107
- .node.sel { background: rgba(74, 240, 224, 0.18); color: #eafdff; }
108
- .node.drop { background: rgba(74, 240, 224, 0.14); box-shadow: inset 0 0 0 1px var(--accent); }
109
- .caret {
110
- flex: 0 0 18px; width: 18px; height: 100%; display: flex; align-items: center; justify-content: center;
111
- color: #6f93b8; user-select: none;
112
- }
113
- .caret svg { width: 14px; height: 14px; }
114
- .nodeName { overflow: hidden; text-overflow: ellipsis; }
115
- .nodeType { color: #6f93b8; margin-left: 6px; }
116
-
117
- /* ── inspector rows + sections ───────────────────────────────────────────── */
118
- .inspector { padding: 6px 12px 16px; font: 12px/1.5 ui-monospace, monospace; }
119
- .section { margin-top: 4px; }
120
- .secHead {
121
- display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin: 10px 0 4px;
122
- color: #9fe7ff; font-weight: 700; border-bottom: 1px solid var(--line);
123
- padding-bottom: 3px; cursor: pointer; user-select: none;
124
- text-transform: uppercase; letter-spacing: 0.04em; font-size: 11px;
125
- }
126
- .secHeadPlain { cursor: default; }
127
- /* The registry description of a section's tag/component: a subtle, lower-case
128
- caption beside the uppercase title. Truncates rather than wrapping so a long
129
- description never pushes the rows down; the full text is in the row tooltips. */
130
- .secDesc {
131
- /* Own line, full width, wrapping: the description explains what the section is
132
- for and is usually worth more than the property names under it. */
133
- flex: 1 0 100%; margin: 2px 0 1px 20px;
134
- color: #8a9db5; font-weight: 400; font-size: 10px; line-height: 1.45;
135
- text-transform: none; letter-spacing: 0;
136
- white-space: normal; overflow-wrap: anywhere;
137
- }
138
- .secCaret { flex: 0 0 14px; width: 14px; display: inline-flex; align-items: center; color: #6f93b8; }
139
- .secCaret svg { width: 13px; height: 13px; }
140
- .secBody { padding: 1px 0 2px; }
141
-
142
- /* Per-component/attachment toolbar (move up / move down / delete). Pushed to the
143
- right of the section header; the buttons stop propagation so a click doesn't
144
- also fold the section. */
145
- .secActions { margin-left: auto; display: inline-flex; align-items: center; gap: 1px; }
146
- .memberBtn {
147
- width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
148
- padding: 0; border: 0; border-radius: 5px;
149
- background: transparent; color: #7d90a8; cursor: pointer;
150
- }
151
- .memberBtn:hover:not(:disabled) { background: rgba(74, 240, 224, 0.14); color: #eafdff; }
152
- .memberBtn:disabled { opacity: 0.28; cursor: default; }
153
- .memberDelete:hover:not(:disabled) { background: rgba(255, 120, 120, 0.16); color: #ff9b9b; }
154
- .memberBtn svg { width: 13px; height: 13px; }
155
- /* The identity row: an active tick and the id, on one line, as Unity's inspector opens. */
156
- .identity { display: flex; align-items: center; gap: 8px; padding: 6px 0 8px; }
157
- .identityId { flex: 1 1 auto; min-width: 0; text-align: left; font-weight: 700; color: #eafdff; }
158
-
159
- .row { display: flex; justify-content: space-between; align-items: center; gap: 10px; min-height: 30px; padding: 2px 0; }
160
- .k { color: #8fa6c4; flex: 0 0 auto; }
161
- .v { color: #e7f0fb; text-align: right; word-break: break-word; display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
162
- .empty { color: #6f8099; padding: 16px 4px; }
163
-
164
- /* An attribute that is NOT written in the source: the widget shows the value the
165
- node will build with, dimmed, so "no attribute in the XML" reads differently
166
- from "explicitly set to this". Typing in it writes the attribute. */
167
- .unsetRow .k { color: #61708a; }
168
- .unsetRow .input, .unsetRow .swatch, .unsetRow .toggle { opacity: 0.45; }
169
- .unsetRow:focus-within .input, .unsetRow:focus-within .swatch, .unsetRow:focus-within .toggle { opacity: 1; }
170
-
171
- /* Clears the attribute (removes it from the file). Only rendered when written. */
172
- .clearBtn {
173
- flex: 0 0 auto; width: 20px; height: 20px; margin-left: 2px;
174
- display: inline-flex; align-items: center; justify-content: center;
175
- padding: 0; border: 0; border-radius: 5px;
176
- background: transparent; color: #6f8099; cursor: pointer;
177
- }
178
- .clearBtn:hover { background: rgba(255, 120, 120, 0.14); color: #ff9b9b; }
179
- .clearBtn svg { width: 12px; height: 12px; }
180
-
181
- /* A disabled/locked row: dimmed value + a small lock glyph (reason in the title). */
182
- .locked { cursor: help; }
183
- .locked .k { color: #6f8099; }
184
- .locked .lockVal { color: #7c8ba3; }
185
- .lockIcon { width: 12px; height: 12px; color: #6f8099; flex: 0 0 auto; opacity: 0.85; }
186
-
187
- /* ── editable field widgets (16px font avoids iOS tap-zoom; roomy touch targets) ── */
188
- .input {
189
- background: #0e1219; color: #e7f0fb; border: 1px solid var(--line); border-radius: 5px;
190
- padding: 4px 6px; font: 16px ui-monospace, monospace; min-height: 28px;
191
- }
192
- .input:focus { outline: none; border-color: var(--accent); }
193
- .num { width: 64px; text-align: right; }
194
- .axes { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
195
- .axis { display: flex; align-items: center; gap: 2px; }
196
- .axisK { color: #6f93b8; font-size: 11px; width: 12px; text-align: center; }
197
- .scrub { cursor: ew-resize; touch-action: none; }
198
- .toggle { width: 22px; height: 22px; accent-color: var(--accent); }
199
- .color { display: flex; align-items: center; gap: 5px; }
200
- .swatch { width: 30px; height: 28px; padding: 0; border: 1px solid var(--line); border-radius: 5px; background: none; cursor: pointer; }
201
- .hex { width: 78px; }
202
- .select { min-width: 96px; cursor: pointer; }
203
-
204
- /* ── add component / attachment picker ─────────────────────────────────────── */
205
- /* Centred modal over a dimming backdrop. Above the sheets (z 7) so it is never
206
- occluded, and capped in height so a long registry scrolls instead of overflowing. */
207
- .modalBackdrop {
208
- position: fixed; inset: 0; z-index: 9;
209
- display: flex; align-items: center; justify-content: center;
210
- background: rgba(4, 8, 14, 0.55); backdrop-filter: blur(2px);
211
- pointer-events: auto;
212
- }
213
- .modal {
214
- width: min(460px, calc(100vw - 32px)); max-height: min(70vh, 560px);
215
- display: flex; flex-direction: column;
216
- background: var(--panel); border: 1px solid var(--edge); border-radius: 12px;
217
- box-shadow: 0 18px 60px rgba(0, 0, 0, 0.6);
218
- overflow: hidden;
219
- }
220
- .modalHead {
221
- flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
222
- padding: 10px 10px 10px 14px; border-bottom: 1px solid var(--line);
223
- }
224
- .modalHead .clearBtn { margin-left: auto; }
225
- .modalFilter { flex: 0 0 auto; margin: 10px 12px 6px; }
226
- .modalList { flex: 1 1 auto; overflow-y: auto; padding: 2px 6px 8px; }
227
- .modalItem {
228
- width: 100%; display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
229
- padding: 7px 8px; border: 0; border-radius: 6px;
230
- background: transparent; color: #e7f0fb; text-align: left; cursor: pointer;
231
- font: inherit;
232
- }
233
- .modalItem:hover { background: rgba(74, 240, 224, 0.12); }
234
- .modalItemTag { font-weight: 700; color: #9fe7ff; }
235
- .modalItemDesc { color: #8a9db5; font-size: 10px; line-height: 1.45; }
236
- /* The kind chip on a file-picker row — `scene` / `prefab` beside the name. */
237
- .pickKind {
238
- margin-left: 8px; padding: 1px 6px; border-radius: 999px;
239
- background: rgba(74, 240, 224, 0.12); color: #7fd8cf;
240
- font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
241
- }
242
-
243
- /* The "+ Add …" row that opens the picker, one per area. */
244
- .addRow {
245
- width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px;
246
- margin: 6px 0 2px; padding: 5px 0; border: 1px dashed var(--line); border-radius: 6px;
247
- background: transparent; color: #7d90a8; cursor: pointer; font: inherit; font-size: 11px;
248
- }
249
- .addRow:hover { border-color: var(--accent); color: #eafdff; }
250
- .addRow svg { width: 13px; height: 13px; }
@@ -1,20 +0,0 @@
1
- import { useLayoutEffect, useRef, useState } from 'react';
2
-
3
- /**
4
- * Track an element's content size via ResizeObserver — react-arborist needs
5
- * explicit pixel width/height, so we measure its (flex) container and feed them in.
6
- */
7
- export function useSize<T extends HTMLElement>(): readonly [React.RefObject<T>, { width: number; height: number }] {
8
- const ref = useRef<T>(null);
9
- const [size, setSize] = useState({ width: 0, height: 0 });
10
- useLayoutEffect(() => {
11
- const el = ref.current;
12
- if (!el) return;
13
- const measure = (): void => setSize({ width: el.clientWidth, height: el.clientHeight });
14
- const ro = new ResizeObserver(measure);
15
- ro.observe(el);
16
- measure();
17
- return () => ro.disconnect();
18
- }, []);
19
- return [ref, size] as const;
20
- }
package/app/values.ts DELETED
@@ -1,137 +0,0 @@
1
- import type { PropInfo, ValueShape } from '@scenoco-three/compiler';
2
-
3
- /**
4
- * A value as the inspector's widgets handle it: a number, a flag, some text, or a list of axes.
5
- *
6
- * The document stores attribute **text**, and the widgets — the number scrubber, the vector row,
7
- * the colour swatch — work in typed values. This is the pair of functions between them, and it
8
- * speaks the DSL's own grammar in both directions: `x: 1; y: 2` for a vector, `#rrggbb` for a
9
- * colour, degrees for a rotation. So what a widget writes is what a person would have written.
10
- */
11
- export type Scalar = number | boolean | string | number[];
12
-
13
- /** Attribute text → a value a widget can edit, or undefined when there is nothing to show. */
14
- export function parseValue(shape: ValueShape, text: string | undefined): Scalar | undefined {
15
- if (text === undefined) return undefined;
16
- switch (shape) {
17
- case 'number': {
18
- const n = Number(text.trim());
19
- return Number.isFinite(n) ? n : 0;
20
- }
21
- case 'boolean':
22
- return text.trim() === 'true';
23
- case 'color':
24
- return colorToNumber(text);
25
- case 'vector2':
26
- return axes(text, 2);
27
- case 'vector3':
28
- case 'euler':
29
- return axes(text, 3);
30
- case 'vector4':
31
- return axes(text, 4);
32
- default:
33
- return text;
34
- }
35
- }
36
-
37
- /** A value from a widget → the text the document gets. */
38
- export function formatValue(shape: ValueShape, value: Scalar): string {
39
- switch (shape) {
40
- case 'number':
41
- return String(round(Number(value)));
42
- case 'boolean':
43
- return value ? 'true' : 'false';
44
- case 'color':
45
- return `#${Math.max(0, Math.round(Number(value))).toString(16).padStart(6, '0').slice(-6)}`;
46
- case 'vector2':
47
- return named(value as number[], ['x', 'y']);
48
- case 'vector3':
49
- case 'euler':
50
- return named(value as number[], ['x', 'y', 'z']);
51
- case 'vector4':
52
- return named(value as number[], ['x', 'y', 'z', 'w']);
53
- default:
54
- return String(value);
55
- }
56
- }
57
-
58
- /**
59
- * Something to render for an attribute the document does not write.
60
- *
61
- * The row still has to draw, and setting it is the whole point of it being there — so an unwritten
62
- * optional shows its declared default when it has one, and a neutral value when it does not.
63
- */
64
- export function shownValue(prop: PropInfo, written: string | undefined): Scalar {
65
- return parseValue(prop.shape, written) ?? parseValue(prop.shape, prop.default?.replace(/^['"]|['"]$/g, '')) ?? neutral(prop.shape);
66
- }
67
-
68
- function neutral(shape: ValueShape): Scalar {
69
- switch (shape) {
70
- case 'boolean':
71
- return false;
72
- case 'string':
73
- case 'enum':
74
- case 'nodeRef':
75
- case 'assetRef':
76
- return '';
77
- case 'vector2':
78
- return [0, 0];
79
- case 'vector3':
80
- case 'euler':
81
- return [0, 0, 0];
82
- case 'vector4':
83
- return [0, 0, 0, 0];
84
- default:
85
- return 0;
86
- }
87
- }
88
-
89
- /** Which widget renders a shape, or `null` for one no widget can edit. */
90
- export function widgetFor(prop: PropInfo): ValueShape | null {
91
- switch (prop.shape) {
92
- case 'number':
93
- case 'boolean':
94
- case 'string':
95
- case 'color':
96
- case 'vector2':
97
- case 'vector3':
98
- case 'vector4':
99
- case 'euler':
100
- case 'enum':
101
- case 'nodeRef':
102
- case 'assetRef':
103
- return prop.shape;
104
- default:
105
- return null; // an array, or something the DSL cannot express
106
- }
107
- }
108
-
109
- function axes(text: string, arity: number): number[] {
110
- const out = new Array<number>(arity).fill(0);
111
- const names = ['x', 'y', 'z', 'w'];
112
- // The one spelling a document uses. A value that is not in it reads as zeroes, which is what
113
- // the compiler would have rejected anyway — the widget then writes it back correctly.
114
- for (const part of text.split(';')) {
115
- const [axis, value] = part.split(':');
116
- const index = names.indexOf((axis ?? '').trim());
117
- const n = Number((value ?? '').trim());
118
- if (index !== -1 && index < arity && Number.isFinite(n)) out[index] = n;
119
- }
120
- return out;
121
- }
122
-
123
- function named(values: number[], names: string[]): string {
124
- return names.map((name, i) => `${name}: ${round(values[i] ?? 0)}`).join('; ');
125
- }
126
-
127
- function colorToNumber(text: string): number {
128
- const hex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(text.trim());
129
- if (!hex) return 0;
130
- const digits = hex[1]!;
131
- return parseInt(digits.length === 3 ? digits.replace(/./g, (c) => c + c) : digits, 16);
132
- }
133
-
134
- /** Three decimals: a scrubbed number should not write `0.30000000000000004` into someone's file. */
135
- function round(n: number): number {
136
- return Math.round(n * 1000) / 1000;
137
- }
package/app/vite-env.d.ts DELETED
@@ -1,7 +0,0 @@
1
- /// <reference types="vite/client" />
2
-
3
- // The editor server's plugin emits two modules: `scenoco-runtime` registers the
4
- // built-in node tags + tag packages, and `scenoco-components` imports every project
5
- // component. They're split so a broken component can't wipe the core tag registry.
6
- declare module 'virtual:scenoco-runtime';
7
- declare module 'virtual:scenoco-components';
package/dist/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * `@scenoco-three/editor` — the programmatic API behind the `scenoco-editor` CLI.
3
- *
4
- * Serves the editor page, bridges it to an MCP server over a WebSocket, and exposes MCP tools
5
- * that **observe**: open, screenshot, telemetry, play/pause/stop, simulate. Documents are changed
6
- * by editing the XML; there is no remote edit tool, by decision.
7
- */
8
- export { startEditorServer, applyEdits, type ServerOptions, type EditorServer, type EditorBridge, type Edit, } from './server.js';
9
- export { createMcpServer, startMcpHttp } from './mcp.js';
10
- export { WS_PATH, commandId, type EditorCommand, type EditorPush, type EditorResponse, type EditorResult, type PreviewFrame, type SimStep, } from './protocol.js';
package/dist/index.js DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * `@scenoco-three/editor` — the programmatic API behind the `scenoco-editor` CLI.
3
- *
4
- * Serves the editor page, bridges it to an MCP server over a WebSocket, and exposes MCP tools
5
- * that **observe**: open, screenshot, telemetry, play/pause/stop, simulate. Documents are changed
6
- * by editing the XML; there is no remote edit tool, by decision.
7
- */
8
- export { startEditorServer, applyEdits, } from './server.js';
9
- export { createMcpServer, startMcpHttp } from './mcp.js';
10
- export { WS_PATH, commandId, } from './protocol.js';
package/dist/mcp.d.ts DELETED
@@ -1,32 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { EditorBridge } from './server.js';
3
- /**
4
- * The MCP surface: **eyes, not hands.**
5
- *
6
- * Open a document, render it, drive it, read what happened. There is no tool here that writes to
7
- * the user's source, and that is a decision rather than an omission. An agent already has file
8
- * tools, and the editor's watcher rebuilds whatever it writes — so an edit tool would have been
9
- * a second write path into someone's project that never passed through their editor, their diff
10
- * or their undo. An agent holding one can reshape a scene while believing it is only looking at
11
- * it.
12
- *
13
- * v1 shipped `patch`, `structural` and `save` tools alongside these. They are deleted.
14
- *
15
- * The one thing worth insisting on: `simulate` exists because a screenshot of frame zero says
16
- * almost nothing about whether a game works. Drive it to the interesting moment, *then* look.
17
- */
18
- export declare function createMcpServer(bridge: EditorBridge, editorUrl: () => string): McpServer;
19
- /**
20
- * Serve the MCP over HTTP on a fixed port.
21
- *
22
- * Fixed on purpose: an MCP client is configured once with a URL, and a port that moved on every
23
- * restart would need reconfiguring every time.
24
- *
25
- * **One transport per session**, created when a client initializes and kept until it goes away.
26
- * A single shared transport looked simpler and allowed exactly one client ever: the first
27
- * `initialize` claimed it, and every later one — a reconnect, a second client, a restarted
28
- * agent — got a bare `400`, which reads like a crashed server rather than a design limit.
29
- */
30
- export declare function startMcpHttp(makeServer: () => McpServer, port: number): Promise<{
31
- close(): Promise<void>;
32
- }>;